Removed fences
This commit is contained in:
parent
ba22c0a583
commit
a6fbd3bfe2
|
@ -195,7 +195,7 @@ void ACarlaGameModeBase::BeginPlay()
|
||||||
|
|
||||||
// OnBeginFrame
|
// OnBeginFrame
|
||||||
// OnEndFrame
|
// OnEndFrame
|
||||||
CaptureAtlasDelegate = FCoreDelegates::OnEndFrame.AddUObject(this, &ACarlaGameModeBase::CaptureAtlas);
|
CaptureAtlasDelegate = FCoreDelegates::OnBeginFrame.AddUObject(this, &ACarlaGameModeBase::CaptureAtlas);
|
||||||
// SendAtlasDelegate = FCoreDelegates::OnEndFrame.AddUObject(this, &ACarlaGameModeBase::SendAtlas);
|
// SendAtlasDelegate = FCoreDelegates::OnEndFrame.AddUObject(this, &ACarlaGameModeBase::SendAtlas);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -209,7 +209,6 @@ void ACarlaGameModeBase::Tick(float DeltaSeconds)
|
||||||
{
|
{
|
||||||
Recorder->Tick(DeltaSeconds);
|
Recorder->Tick(DeltaSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ACarlaGameModeBase::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
void ACarlaGameModeBase::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||||
|
@ -225,6 +224,7 @@ void ACarlaGameModeBase::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: improve this delete processs; not multithread friendly
|
// TODO: improve this delete processs; not multithread friendly
|
||||||
|
/*
|
||||||
while(!AtlasCopyRequestQueue.IsEmpty())
|
while(!AtlasCopyRequestQueue.IsEmpty())
|
||||||
{
|
{
|
||||||
delete AtlasCopyRequestQueue.Peek();
|
delete AtlasCopyRequestQueue.Peek();
|
||||||
|
@ -236,6 +236,7 @@ void ACarlaGameModeBase::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||||
delete AtlasCopyRequestsQueuePool.Peek();
|
delete AtlasCopyRequestsQueuePool.Peek();
|
||||||
AtlasCopyRequestsQueuePool.Pop();
|
AtlasCopyRequestsQueuePool.Pop();
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
// OnEndFrame
|
// OnEndFrame
|
||||||
|
@ -395,7 +396,7 @@ void ACarlaGameModeBase::DebugShowSignals(bool enable)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
FAtlasCopyRequest* ACarlaGameModeBase::GetAtlasCopyRequest()
|
FAtlasCopyRequest* ACarlaGameModeBase::GetAtlasCopyRequest()
|
||||||
{
|
{
|
||||||
if(AtlasCopyRequestsQueuePool.IsEmpty()) {
|
if(AtlasCopyRequestsQueuePool.IsEmpty()) {
|
||||||
|
@ -406,7 +407,7 @@ FAtlasCopyRequest* ACarlaGameModeBase::GetAtlasCopyRequest()
|
||||||
AtlasCopyRequestsQueuePool.Dequeue(AtlasCopyRequest);
|
AtlasCopyRequestsQueuePool.Dequeue(AtlasCopyRequest);
|
||||||
return AtlasCopyRequest;
|
return AtlasCopyRequest;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
void ACarlaGameModeBase::CreateAtlasTextures()
|
void ACarlaGameModeBase::CreateAtlasTextures()
|
||||||
{
|
{
|
||||||
if(AtlasTextureWidth > 0 && AtlasTextureHeight > 0)
|
if(AtlasTextureWidth > 0 && AtlasTextureHeight > 0)
|
||||||
|
@ -414,17 +415,19 @@ void ACarlaGameModeBase::CreateAtlasTextures()
|
||||||
UE_LOG(LogCarla, Warning, TEXT("ACarlaGameModeBase::CreateAtlasTextures %d %dx%d"), SceneCaptureSensors.Num(), AtlasTextureWidth, AtlasTextureHeight);
|
UE_LOG(LogCarla, Warning, TEXT("ACarlaGameModeBase::CreateAtlasTextures %d %dx%d"), SceneCaptureSensors.Num(), AtlasTextureWidth, AtlasTextureHeight);
|
||||||
|
|
||||||
FRHIResourceCreateInfo CreateInfo;
|
FRHIResourceCreateInfo CreateInfo;
|
||||||
|
CamerasAtlasTexture = RHICreateTexture2D(AtlasTextureWidth, AtlasTextureHeight, PF_B8G8R8A8, 1, 1, TexCreate_CPUReadback, CreateInfo);
|
||||||
|
|
||||||
for(int i = 0; i < kMaxNumTextures; i++)
|
for(int i = 0; i < kMaxNumTextures; i++)
|
||||||
{
|
{
|
||||||
CamerasAtlasTexture[i] = RHICreateTexture2D(AtlasTextureWidth, AtlasTextureHeight, PF_B8G8R8A8, 1, 1, TexCreate_CPUReadback, CreateInfo);
|
AtlasImage[i].Init(FColor(), AtlasTextureWidth * AtlasTextureHeight);
|
||||||
// Prepare some AtlasCopyRequests
|
// Prepare some AtlasCopyRequests
|
||||||
AtlasCopyRequestQueue.Enqueue(new FAtlasCopyRequest());
|
//AtlasCopyRequestQueue.Enqueue(new FAtlasCopyRequest());
|
||||||
}
|
}
|
||||||
IsAtlasTextureValid = true;
|
IsAtlasTextureValid = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern FDynamicRHI* GDynamicRHI;
|
//extern FDynamicRHI* GDynamicRHI;
|
||||||
|
|
||||||
void ACarlaGameModeBase::CaptureAtlas()
|
void ACarlaGameModeBase::CaptureAtlas()
|
||||||
{
|
{
|
||||||
|
@ -452,15 +455,15 @@ void ACarlaGameModeBase::CaptureAtlas()
|
||||||
//}
|
//}
|
||||||
//UE_LOG(LogCarla, Warning, TEXT("ACarlaGameModeBase::CaptureAtlas: creating request"));
|
//UE_LOG(LogCarla, Warning, TEXT("ACarlaGameModeBase::CaptureAtlas: creating request"));
|
||||||
|
|
||||||
FAtlasCopyRequest* AtlasCopyRequest = GetAtlasCopyRequest();
|
//FAtlasCopyRequest* AtlasCopyRequest = GetAtlasCopyRequest();
|
||||||
AtlasCopyRequest->ResizeBuffer(AtlasTextureWidth, AtlasTextureHeight);
|
//AtlasCopyRequest->ResizeBuffer(AtlasTextureWidth, AtlasTextureHeight);
|
||||||
|
|
||||||
// Download Atlas texture
|
// Download Atlas texture
|
||||||
ENQUEUE_RENDER_COMMAND(ACarlaGameModeBase_CaptureAtlas)
|
ENQUEUE_RENDER_COMMAND(ACarlaGameModeBase_CaptureAtlas)
|
||||||
(
|
(
|
||||||
[This, &CurrentAtlasPixels = AtlasCopyRequest->AtlasImage](FRHICommandListImmediate& RHICmdList) mutable
|
[This](FRHICommandListImmediate& RHICmdList) mutable
|
||||||
{
|
{
|
||||||
FTexture2DRHIRef AtlasTexture = This->CamerasAtlasTexture[This->PreviousAtlas];
|
FTexture2DRHIRef AtlasTexture = This->CamerasAtlasTexture;
|
||||||
|
|
||||||
if (!AtlasTexture)
|
if (!AtlasTexture)
|
||||||
{
|
{
|
||||||
|
@ -471,9 +474,7 @@ void ACarlaGameModeBase::CaptureAtlas()
|
||||||
FIntRect Rect = FIntRect(0, 0, This->AtlasTextureWidth, This->AtlasTextureHeight);
|
FIntRect Rect = FIntRect(0, 0, This->AtlasTextureWidth, This->AtlasTextureHeight);
|
||||||
|
|
||||||
#if !UE_BUILD_SHIPPING
|
#if !UE_BUILD_SHIPPING
|
||||||
if(This->ReadSurfaceMode == 2) {
|
if(This->ReadSurfaceMode == 2) Rect = FIntRect(0, 0, This->SurfaceW, This->SurfaceH);
|
||||||
Rect = FIntRect(0, 0, This->SurfaceW, This->SurfaceH);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
// GDynamicRHI->RHIReadSurfaceData(AtlasTexture, Rect, CurrentAtlasPixels, FReadSurfaceDataFlags(RCM_UNorm, CubeFace_MAX));
|
// GDynamicRHI->RHIReadSurfaceData(AtlasTexture, Rect, CurrentAtlasPixels, FReadSurfaceDataFlags(RCM_UNorm, CubeFace_MAX));
|
||||||
|
|
||||||
|
@ -485,17 +486,17 @@ void ACarlaGameModeBase::CaptureAtlas()
|
||||||
RHICmdList.ReadSurfaceData(
|
RHICmdList.ReadSurfaceData(
|
||||||
AtlasTexture,
|
AtlasTexture,
|
||||||
Rect,
|
Rect,
|
||||||
CurrentAtlasPixels,
|
This->AtlasImage[This->CurrentAtlas],
|
||||||
FReadSurfaceDataFlags(RCM_UNorm, CubeFace_MAX));
|
FReadSurfaceDataFlags(RCM_UNorm, CubeFace_MAX));
|
||||||
|
|
||||||
// This->PreviousAtlas = This->CurrentAtlas;
|
This->PreviousAtlas = This->CurrentAtlas;
|
||||||
// This->CurrentAtlas = (This->CurrentAtlas + 1) & ~kMaxNumTextures;
|
This->CurrentAtlas = (This->CurrentAtlas + 1) & ~kMaxNumTextures;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
AtlasCopyRequest->Start();
|
//AtlasCopyRequest->Start();
|
||||||
// AtlasCopyRequest->Wait();
|
// AtlasCopyRequest->Wait();
|
||||||
AtlasCopyRequestQueue.Enqueue(AtlasCopyRequest);
|
//AtlasCopyRequestQueue.Enqueue(AtlasCopyRequest);
|
||||||
|
|
||||||
SendAtlas();
|
SendAtlas();
|
||||||
|
|
||||||
|
@ -503,21 +504,21 @@ void ACarlaGameModeBase::CaptureAtlas()
|
||||||
|
|
||||||
void ACarlaGameModeBase::SendAtlas()
|
void ACarlaGameModeBase::SendAtlas()
|
||||||
{
|
{
|
||||||
if(!AtlasCopyRequestQueue.IsEmpty())
|
//if(!AtlasCopyRequestQueue.IsEmpty())
|
||||||
{
|
{
|
||||||
FAtlasCopyRequest* AtlasCopyRequest = nullptr;
|
//FAtlasCopyRequest* AtlasCopyRequest = nullptr;
|
||||||
AtlasCopyRequestQueue.Peek(AtlasCopyRequest);
|
//AtlasCopyRequestQueue.Peek(AtlasCopyRequest);
|
||||||
|
|
||||||
// Be sure that the request has finished
|
// Be sure that the request has finished
|
||||||
|
|
||||||
if( AtlasCopyRequest && AtlasCopyRequest->IsComplete() )
|
//if( AtlasCopyRequest && AtlasCopyRequest->IsComplete() )
|
||||||
{
|
{
|
||||||
//UE_LOG(LogCarla, Warning, TEXT("ACarlaGameModeBase::SendAtlas: DONE"));
|
//UE_LOG(LogCarla, Warning, TEXT("ACarlaGameModeBase::SendAtlas: DONE"));
|
||||||
#if !UE_BUILD_SHIPPING
|
#if !UE_BUILD_SHIPPING
|
||||||
if(!AtlasCopyToCamera)
|
if(!AtlasCopyToCamera)
|
||||||
{
|
{
|
||||||
AtlasCopyRequestQueue.Pop();
|
//AtlasCopyRequestQueue.Pop();
|
||||||
AtlasCopyRequestsQueuePool.Enqueue(AtlasCopyRequest);
|
//AtlasCopyRequestsQueuePool.Enqueue(AtlasCopyRequest);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -531,16 +532,18 @@ void ACarlaGameModeBase::SendAtlas()
|
||||||
for(int32 Index = 0; Index < SceneCaptureSensors.Num(); Index++)
|
for(int32 Index = 0; Index < SceneCaptureSensors.Num(); Index++)
|
||||||
{
|
{
|
||||||
ASceneCaptureSensor* Sensor = SceneCaptureSensors[Index];
|
ASceneCaptureSensor* Sensor = SceneCaptureSensors[Index];
|
||||||
Sensor->CopyTextureFromAtlas(AtlasCopyRequest->AtlasImage, AtlasTextureWidth);
|
// TODO: merge this functions and avoid double copy
|
||||||
|
Sensor->CopyTextureFromAtlas(AtlasImage[PreviousAtlas], AtlasTextureWidth);
|
||||||
|
Sensor->SendPixels();
|
||||||
}
|
}
|
||||||
//);
|
//);
|
||||||
|
|
||||||
// Remove from pending queue ...
|
// Remove from pending queue ...
|
||||||
AtlasCopyRequestQueue.Pop();
|
//AtlasCopyRequestQueue.Pop();
|
||||||
// ... add to pool
|
// ... add to pool
|
||||||
AtlasCopyRequestsQueuePool.Enqueue(AtlasCopyRequest);
|
//AtlasCopyRequestsQueuePool.Enqueue(AtlasCopyRequest);
|
||||||
}
|
}
|
||||||
else
|
//else
|
||||||
{
|
{
|
||||||
//UE_LOG(LogCarla, Error, TEXT("ACarlaGameModeBase::SendAtlas: request didn't finish"));
|
//UE_LOG(LogCarla, Error, TEXT("ACarlaGameModeBase::SendAtlas: request didn't finish"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
#include "CarlaGameModeBase.generated.h"
|
#include "CarlaGameModeBase.generated.h"
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
USTRUCT()
|
USTRUCT()
|
||||||
struct FAtlasCopyRequest
|
struct FAtlasCopyRequest
|
||||||
{
|
{
|
||||||
|
@ -63,6 +64,7 @@ struct FAtlasCopyRequest
|
||||||
uint32 AtlasTextureWidth = 0u;
|
uint32 AtlasTextureWidth = 0u;
|
||||||
uint32 AtlasTextureHeight = 0u;
|
uint32 AtlasTextureHeight = 0u;
|
||||||
};
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
/// Base class for the CARLA Game Mode.
|
/// Base class for the CARLA Game Mode.
|
||||||
|
@ -101,7 +103,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
FTexture2DRHIRef GetCurrentCamerasAtlasTexture() const{
|
FTexture2DRHIRef GetCurrentCamerasAtlasTexture() const{
|
||||||
return CamerasAtlasTexture[CurrentAtlas];
|
return CamerasAtlasTexture;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32 GetAtlasTextureWidth() const {
|
uint32 GetAtlasTextureWidth() const {
|
||||||
|
@ -178,7 +180,7 @@ private:
|
||||||
|
|
||||||
void ParseOpenDrive(const FString &MapName);
|
void ParseOpenDrive(const FString &MapName);
|
||||||
|
|
||||||
FAtlasCopyRequest* GetAtlasCopyRequest();
|
//FAtlasCopyRequest* GetAtlasCopyRequest();
|
||||||
|
|
||||||
void CreateAtlasTextures();
|
void CreateAtlasTextures();
|
||||||
|
|
||||||
|
@ -221,13 +223,16 @@ private:
|
||||||
FDelegateHandle CaptureAtlasDelegate;
|
FDelegateHandle CaptureAtlasDelegate;
|
||||||
FDelegateHandle SendAtlasDelegate;
|
FDelegateHandle SendAtlasDelegate;
|
||||||
|
|
||||||
TQueue<FAtlasCopyRequest*> AtlasCopyRequestQueue;
|
//TQueue<FAtlasCopyRequest*> AtlasCopyRequestQueue;
|
||||||
TQueue<FAtlasCopyRequest*> AtlasCopyRequestsQueuePool;
|
//TQueue<FAtlasCopyRequest*> AtlasCopyRequestsQueuePool;
|
||||||
|
|
||||||
// TODO: clean
|
// TODO: clean
|
||||||
|
|
||||||
|
// TODO: remove kMaxNumTextures and current/prev index
|
||||||
static const uint32 kMaxNumTextures = 1u; // This has to be POT
|
static const uint32 kMaxNumTextures = 1u; // This has to be POT
|
||||||
TArray<ASceneCaptureSensor*> SceneCaptureSensors;
|
TArray<ASceneCaptureSensor*> SceneCaptureSensors;
|
||||||
FTexture2DRHIRef CamerasAtlasTexture[kMaxNumTextures];
|
FTexture2DRHIRef CamerasAtlasTexture;
|
||||||
|
TArray<FColor> AtlasImage[kMaxNumTextures];
|
||||||
uint32 AtlasTextureWidth = 0u;
|
uint32 AtlasTextureWidth = 0u;
|
||||||
uint32 AtlasTextureHeight = 0u;
|
uint32 AtlasTextureHeight = 0u;
|
||||||
uint32 CurrentAtlasTextureHeight = 0u;
|
uint32 CurrentAtlasTextureHeight = 0u;
|
||||||
|
|
|
@ -30,10 +30,8 @@ ADepthCamera::ADepthCamera(const FObjectInitializer &ObjectInitializer)
|
||||||
Offset = carla::sensor::SensorRegistry::get<ADepthCamera*>::type::header_offset;
|
Offset = carla::sensor::SensorRegistry::get<ADepthCamera*>::type::header_offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADepthCamera::Tick(float DeltaTime)
|
void ADepthCamera::SendPixels()
|
||||||
{
|
{
|
||||||
Super::Tick(DeltaTime);
|
|
||||||
|
|
||||||
#if !UE_BUILD_SHIPPING
|
#if !UE_BUILD_SHIPPING
|
||||||
ACarlaGameModeBase* GameMode = Cast<ACarlaGameModeBase>(GetWorld()->GetAuthGameMode());
|
ACarlaGameModeBase* GameMode = Cast<ACarlaGameModeBase>(GetWorld()->GetAuthGameMode());
|
||||||
if(!GameMode->IsCameraStreamEnabled()) return;
|
if(!GameMode->IsCameraStreamEnabled()) return;
|
||||||
|
|
|
@ -26,5 +26,5 @@ public:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
void Tick(float DeltaTime) override;
|
void SendPixels() override;
|
||||||
};
|
};
|
||||||
|
|
|
@ -24,10 +24,8 @@ ASceneCaptureCamera::ASceneCaptureCamera(const FObjectInitializer &ObjectInitial
|
||||||
Offset = carla::sensor::SensorRegistry::get<ASceneCaptureCamera*>::type::header_offset;
|
Offset = carla::sensor::SensorRegistry::get<ASceneCaptureCamera*>::type::header_offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ASceneCaptureCamera::Tick(float DeltaTime)
|
void ASceneCaptureCamera::SendPixels()
|
||||||
{
|
{
|
||||||
Super::Tick(DeltaTime);
|
|
||||||
|
|
||||||
#if !UE_BUILD_SHIPPING
|
#if !UE_BUILD_SHIPPING
|
||||||
ACarlaGameModeBase* GameMode = Cast<ACarlaGameModeBase>(GetWorld()->GetAuthGameMode());
|
ACarlaGameModeBase* GameMode = Cast<ACarlaGameModeBase>(GetWorld()->GetAuthGameMode());
|
||||||
if(!GameMode->IsCameraStreamEnabled()) return;
|
if(!GameMode->IsCameraStreamEnabled()) return;
|
||||||
|
|
|
@ -26,6 +26,6 @@ public:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
void Tick(float DeltaTime) override;
|
void SendPixels() override;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -565,7 +565,14 @@ void ASceneCaptureSensor::CopyTextureToAtlas()
|
||||||
void ASceneCaptureSensor::CopyTextureFromAtlas(const TArray<FColor>& AtlasImage, uint32 AtlasTextureWidth)
|
void ASceneCaptureSensor::CopyTextureFromAtlas(const TArray<FColor>& AtlasImage, uint32 AtlasTextureWidth)
|
||||||
{
|
{
|
||||||
|
|
||||||
if(AtlasImage.Num() > 0 && ImageToSend.Num() > 0)
|
// Check that the atlas alreay contains our texture
|
||||||
|
// and our image has been initialized
|
||||||
|
if(AtlasImage.Num() < (PositionInAtlas.Y * AtlasTextureWidth + ImageWidth * ImageHeight) ||
|
||||||
|
ImageToSend.Num() <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
SCOPE_CYCLE_COUNTER(STAT_CarlaSensorBufferCopy);
|
SCOPE_CYCLE_COUNTER(STAT_CarlaSensorBufferCopy);
|
||||||
|
|
||||||
|
|
|
@ -274,6 +274,8 @@ public:
|
||||||
|
|
||||||
void CopyTextureFromAtlas(const TArray<FColor>& AtlasImage, uint32 AtlasTextureWidth);
|
void CopyTextureFromAtlas(const TArray<FColor>& AtlasImage, uint32 AtlasTextureWidth);
|
||||||
|
|
||||||
|
virtual void SendPixels() {}
|
||||||
|
|
||||||
template <typename TSensor>
|
template <typename TSensor>
|
||||||
void SendPixelsInStream(TSensor &Sensor)
|
void SendPixelsInStream(TSensor &Sensor)
|
||||||
{
|
{
|
||||||
|
|
|
@ -26,10 +26,8 @@ ASemanticSegmentationCamera::ASemanticSegmentationCamera(
|
||||||
Offset = carla::sensor::SensorRegistry::get<ASemanticSegmentationCamera*>::type::header_offset;
|
Offset = carla::sensor::SensorRegistry::get<ASemanticSegmentationCamera*>::type::header_offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ASemanticSegmentationCamera::Tick(float DeltaTime)
|
void ASemanticSegmentationCamera::SendPixels()
|
||||||
{
|
{
|
||||||
Super::Tick(DeltaTime);
|
|
||||||
|
|
||||||
#if !UE_BUILD_SHIPPING
|
#if !UE_BUILD_SHIPPING
|
||||||
ACarlaGameModeBase* GameMode = Cast<ACarlaGameModeBase>(GetWorld()->GetAuthGameMode());
|
ACarlaGameModeBase* GameMode = Cast<ACarlaGameModeBase>(GetWorld()->GetAuthGameMode());
|
||||||
if(!GameMode->IsCameraStreamEnabled()) return;
|
if(!GameMode->IsCameraStreamEnabled()) return;
|
||||||
|
|
|
@ -26,5 +26,5 @@ public:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
void Tick(float DeltaTime) override;
|
void SendPixels() override;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue