Async render passes
This commit is contained in:
Vendored
+1
-3
@@ -30,15 +30,13 @@
|
|||||||
"UNICODE",
|
"UNICODE",
|
||||||
"_UNICODE"
|
"_UNICODE"
|
||||||
],
|
],
|
||||||
"intelliSenseMode": "linux-gcc-x64",
|
|
||||||
"configurationProvider": "ms-vscode.cmake-tools",
|
"configurationProvider": "ms-vscode.cmake-tools",
|
||||||
"cppStandard": "c++20",
|
"cppStandard": "c++20",
|
||||||
"browse": {
|
"browse": {
|
||||||
"path": [
|
"path": [
|
||||||
"external/**"
|
"external/**"
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
"compilerPath": "/usr/bin/g++"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"version": 4
|
"version": 4
|
||||||
|
|||||||
Vendored
-1
@@ -15,7 +15,6 @@
|
|||||||
"console": "internalConsole",
|
"console": "internalConsole",
|
||||||
"environment": [],
|
"environment": [],
|
||||||
"externalConsole": false,
|
"externalConsole": false,
|
||||||
"MIMode": "gdb",
|
|
||||||
"setupCommands": [
|
"setupCommands": [
|
||||||
{
|
{
|
||||||
"description": "Enable break on all exceptions",
|
"description": "Enable break on all exceptions",
|
||||||
|
|||||||
@@ -208,9 +208,10 @@ public:
|
|||||||
deallocateNode(tmp->prev);
|
deallocateNode(tmp->prev);
|
||||||
}
|
}
|
||||||
deallocateNode(tail);
|
deallocateNode(tail);
|
||||||
markIteratorDirty();
|
|
||||||
tail = nullptr;
|
tail = nullptr;
|
||||||
root = nullptr;
|
root = nullptr;
|
||||||
|
markIteratorDirty();
|
||||||
|
_size = 0;
|
||||||
}
|
}
|
||||||
//Insert at the end
|
//Insert at the end
|
||||||
iterator add(const T &value)
|
iterator add(const T &value)
|
||||||
@@ -291,7 +292,7 @@ public:
|
|||||||
{
|
{
|
||||||
value_type temp = std::move(root->data);
|
value_type temp = std::move(root->data);
|
||||||
popFront();
|
popFront();
|
||||||
return std::move(temp);
|
return temp;
|
||||||
}
|
}
|
||||||
iterator remove(iterator pos)
|
iterator remove(iterator pos)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,13 +20,13 @@ BasePassMeshProcessor::~BasePassMeshProcessor()
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void BasePassMeshProcessor::addMeshBatch(
|
Job BasePassMeshProcessor::processMeshBatch(
|
||||||
const MeshBatch& batch,
|
const MeshBatch& batch,
|
||||||
// const PPrimitiveComponent primitiveComponent,
|
// const PPrimitiveComponent primitiveComponent,
|
||||||
const Gfx::PRenderPass renderPass,
|
const Gfx::PRenderPass& renderPass,
|
||||||
Gfx::PPipelineLayout pipelineLayout,
|
Gfx::PPipelineLayout pipelineLayout,
|
||||||
Gfx::PDescriptorLayout primitiveLayout,
|
Gfx::PDescriptorLayout primitiveLayout,
|
||||||
Array<Gfx::PDescriptorSet>& descriptorSets,
|
Array<Gfx::PDescriptorSet> descriptorSets,
|
||||||
int32 /*staticMeshId*/)
|
int32 /*staticMeshId*/)
|
||||||
{
|
{
|
||||||
PMaterialAsset material = batch.material;
|
PMaterialAsset material = batch.material;
|
||||||
@@ -34,11 +34,12 @@ void BasePassMeshProcessor::addMeshBatch(
|
|||||||
|
|
||||||
const PVertexShaderInput vertexInput = batch.vertexInput;
|
const PVertexShaderInput vertexInput = batch.vertexInput;
|
||||||
|
|
||||||
const Gfx::ShaderCollection* collection = material->getShaders(Gfx::RenderPassType::BasePass, vertexInput->getType());
|
const Gfx::ShaderCollection* collection = material->getShaders(Gfx::RenderPassType::BasePass, vertexInput->getType());
|
||||||
assert(collection != nullptr);
|
assert(collection != nullptr);
|
||||||
|
|
||||||
Gfx::PRenderCommand renderCommand = graphics->createRenderCommand();
|
Gfx::PRenderCommand renderCommand = graphics->createRenderCommand();
|
||||||
renderCommand->setViewport(target);
|
renderCommand->setViewport(target);
|
||||||
|
|
||||||
pipelineLayout->addDescriptorLayout(BasePass::INDEX_MATERIAL, material->getDescriptorLayout());
|
pipelineLayout->addDescriptorLayout(BasePass::INDEX_MATERIAL, material->getDescriptorLayout());
|
||||||
pipelineLayout->create();
|
pipelineLayout->create();
|
||||||
Gfx::PDescriptorSet materialSet = material->createDescriptorSet();
|
Gfx::PDescriptorSet materialSet = material->createDescriptorSet();
|
||||||
@@ -63,6 +64,7 @@ void BasePassMeshProcessor::addMeshBatch(
|
|||||||
false);
|
false);
|
||||||
}
|
}
|
||||||
renderCommands.add(renderCommand);
|
renderCommands.add(renderCommand);
|
||||||
|
co_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Array<Gfx::PRenderCommand> BasePassMeshProcessor::getRenderCommands()
|
Array<Gfx::PRenderCommand> BasePassMeshProcessor::getRenderCommands()
|
||||||
@@ -120,7 +122,7 @@ BasePass::~BasePass()
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void BasePass::beginFrame()
|
Job BasePass::beginFrame()
|
||||||
{
|
{
|
||||||
processor->clearCommands();
|
processor->clearCommands();
|
||||||
primitiveLayout->reset();
|
primitiveLayout->reset();
|
||||||
@@ -140,9 +142,10 @@ void BasePass::beginFrame()
|
|||||||
descriptorSets[INDEX_VIEW_PARAMS] = viewLayout->allocateDescriptorSet();
|
descriptorSets[INDEX_VIEW_PARAMS] = viewLayout->allocateDescriptorSet();
|
||||||
descriptorSets[INDEX_VIEW_PARAMS]->updateBuffer(0, viewParamBuffer);
|
descriptorSets[INDEX_VIEW_PARAMS]->updateBuffer(0, viewParamBuffer);
|
||||||
descriptorSets[INDEX_VIEW_PARAMS]->writeChanges();
|
descriptorSets[INDEX_VIEW_PARAMS]->writeChanges();
|
||||||
|
co_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BasePass::render()
|
Job BasePass::render()
|
||||||
{
|
{
|
||||||
oLightIndexList->pipelineBarrier(
|
oLightIndexList->pipelineBarrier(
|
||||||
Gfx::SE_ACCESS_SHADER_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
Gfx::SE_ACCESS_SHADER_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||||
@@ -159,16 +162,23 @@ void BasePass::render()
|
|||||||
descriptorSets[INDEX_LIGHT_ENV]->updateTexture(5, oLightGrid);
|
descriptorSets[INDEX_LIGHT_ENV]->updateTexture(5, oLightGrid);
|
||||||
descriptorSets[INDEX_LIGHT_ENV]->writeChanges();
|
descriptorSets[INDEX_LIGHT_ENV]->writeChanges();
|
||||||
graphics->beginRenderPass(renderPass);
|
graphics->beginRenderPass(renderPass);
|
||||||
|
List<Job> jobs;
|
||||||
for (auto &&meshBatch : passData.staticDrawList)
|
for (auto &&meshBatch : passData.staticDrawList)
|
||||||
{
|
{
|
||||||
processor->addMeshBatch(meshBatch, renderPass, basePassLayout, primitiveLayout, descriptorSets);
|
jobs.add(processor->processMeshBatch(meshBatch, renderPass, basePassLayout, primitiveLayout, descriptorSets));
|
||||||
|
}
|
||||||
|
for(auto& job : jobs)
|
||||||
|
{
|
||||||
|
co_await job;
|
||||||
}
|
}
|
||||||
graphics->executeCommands(processor->getRenderCommands());
|
graphics->executeCommands(processor->getRenderCommands());
|
||||||
graphics->endRenderPass();
|
graphics->endRenderPass();
|
||||||
|
co_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BasePass::endFrame()
|
Job BasePass::endFrame()
|
||||||
{
|
{
|
||||||
|
co_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BasePass::publishOutputs()
|
void BasePass::publishOutputs()
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ public:
|
|||||||
BasePassMeshProcessor(Gfx::PViewport viewport, Gfx::PGraphics graphics, uint8 translucentBasePass);
|
BasePassMeshProcessor(Gfx::PViewport viewport, Gfx::PGraphics graphics, uint8 translucentBasePass);
|
||||||
virtual ~BasePassMeshProcessor();
|
virtual ~BasePassMeshProcessor();
|
||||||
|
|
||||||
virtual void addMeshBatch(
|
virtual Job processMeshBatch(
|
||||||
const MeshBatch& batch,
|
const MeshBatch& batch,
|
||||||
// const PPrimitiveComponent primitiveComponent,
|
// const PPrimitiveComponent primitiveComponent,
|
||||||
const Gfx::PRenderPass renderPass,
|
const Gfx::PRenderPass& renderPass,
|
||||||
Gfx::PPipelineLayout pipelineLayout,
|
Gfx::PPipelineLayout pipelineLayout,
|
||||||
Gfx::PDescriptorLayout primitiveLayout,
|
Gfx::PDescriptorLayout primitiveLayout,
|
||||||
Array<Gfx::PDescriptorSet>& descriptorSets,
|
Array<Gfx::PDescriptorSet> descriptorSets,
|
||||||
int32 staticMeshId = -1) override;
|
int32 staticMeshId = -1) override;
|
||||||
Array<Gfx::PRenderCommand> getRenderCommands();
|
Array<Gfx::PRenderCommand> getRenderCommands();
|
||||||
void clearCommands();
|
void clearCommands();
|
||||||
@@ -40,9 +40,9 @@ class BasePass : public RenderPass<BasePassData>
|
|||||||
public:
|
public:
|
||||||
BasePass(Gfx::PGraphics graphics, Gfx::PViewport viewport, PCameraActor source);
|
BasePass(Gfx::PGraphics graphics, Gfx::PViewport viewport, PCameraActor source);
|
||||||
virtual ~BasePass();
|
virtual ~BasePass();
|
||||||
virtual void beginFrame() override;
|
virtual Job beginFrame() override;
|
||||||
virtual void render() override;
|
virtual Job render() override;
|
||||||
virtual void endFrame() override;
|
virtual Job endFrame() override;
|
||||||
virtual void publishOutputs() override;
|
virtual void publishOutputs() override;
|
||||||
virtual void createRenderPass() override;
|
virtual void createRenderPass() override;
|
||||||
static void modifyRenderPassMacros(Map<const char*, const char*>& defines);
|
static void modifyRenderPassMacros(Map<const char*, const char*>& defines);
|
||||||
|
|||||||
@@ -18,13 +18,13 @@ DepthPrepassMeshProcessor::~DepthPrepassMeshProcessor()
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void DepthPrepassMeshProcessor::addMeshBatch(
|
Job DepthPrepassMeshProcessor::processMeshBatch(
|
||||||
const MeshBatch& batch,
|
const MeshBatch& batch,
|
||||||
// const PPrimitiveComponent primitiveComponent,
|
// const PPrimitiveComponent primitiveComponent,
|
||||||
const Gfx::PRenderPass renderPass,
|
const Gfx::PRenderPass& renderPass,
|
||||||
Gfx::PPipelineLayout pipelineLayout,
|
Gfx::PPipelineLayout pipelineLayout,
|
||||||
Gfx::PDescriptorLayout primitiveLayout,
|
Gfx::PDescriptorLayout primitiveLayout,
|
||||||
Array<Gfx::PDescriptorSet>& descriptorSets,
|
Array<Gfx::PDescriptorSet> descriptorSets,
|
||||||
int32 /*staticMeshId*/)
|
int32 /*staticMeshId*/)
|
||||||
{
|
{
|
||||||
PMaterialAsset material = batch.material;
|
PMaterialAsset material = batch.material;
|
||||||
@@ -61,6 +61,7 @@ void DepthPrepassMeshProcessor::addMeshBatch(
|
|||||||
true);
|
true);
|
||||||
}
|
}
|
||||||
renderCommands.add(renderCommand);
|
renderCommands.add(renderCommand);
|
||||||
|
co_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Array<Gfx::PRenderCommand> DepthPrepassMeshProcessor::getRenderCommands()
|
Array<Gfx::PRenderCommand> DepthPrepassMeshProcessor::getRenderCommands()
|
||||||
@@ -104,7 +105,7 @@ DepthPrepass::~DepthPrepass()
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void DepthPrepass::beginFrame()
|
Job DepthPrepass::beginFrame()
|
||||||
{
|
{
|
||||||
processor->clearCommands();
|
processor->clearCommands();
|
||||||
primitiveLayout->reset();
|
primitiveLayout->reset();
|
||||||
@@ -122,26 +123,33 @@ void DepthPrepass::beginFrame()
|
|||||||
descriptorSets[INDEX_VIEW_PARAMS] = viewLayout->allocateDescriptorSet();
|
descriptorSets[INDEX_VIEW_PARAMS] = viewLayout->allocateDescriptorSet();
|
||||||
descriptorSets[INDEX_VIEW_PARAMS]->updateBuffer(0, viewParamBuffer);
|
descriptorSets[INDEX_VIEW_PARAMS]->updateBuffer(0, viewParamBuffer);
|
||||||
descriptorSets[INDEX_VIEW_PARAMS]->writeChanges();
|
descriptorSets[INDEX_VIEW_PARAMS]->writeChanges();
|
||||||
|
co_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DepthPrepass::render()
|
Job DepthPrepass::render()
|
||||||
{
|
{
|
||||||
|
|
||||||
depthAttachment->getTexture()->pipelineBarrier(
|
depthAttachment->getTexture()->pipelineBarrier(
|
||||||
Gfx::SE_ACCESS_SHADER_READ_BIT, Gfx::SE_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
Gfx::SE_ACCESS_SHADER_READ_BIT, Gfx::SE_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||||
Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT);
|
Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT);
|
||||||
depthAttachment->getTexture()->transferOwnership(Gfx::QueueType::GRAPHICS);
|
depthAttachment->getTexture()->transferOwnership(Gfx::QueueType::GRAPHICS);
|
||||||
graphics->beginRenderPass(renderPass);
|
graphics->beginRenderPass(renderPass);
|
||||||
|
List<Job> jobs;
|
||||||
for (auto &&meshBatch : passData.staticDrawList)
|
for (auto &&meshBatch : passData.staticDrawList)
|
||||||
{
|
{
|
||||||
processor->addMeshBatch(meshBatch, renderPass, depthPrepassLayout, primitiveLayout, descriptorSets);
|
jobs.add(processor->processMeshBatch(meshBatch, renderPass, depthPrepassLayout, primitiveLayout, descriptorSets));
|
||||||
|
}
|
||||||
|
for(auto& job : jobs)
|
||||||
|
{
|
||||||
|
co_await job;
|
||||||
}
|
}
|
||||||
graphics->executeCommands(processor->getRenderCommands());
|
graphics->executeCommands(processor->getRenderCommands());
|
||||||
graphics->endRenderPass();
|
graphics->endRenderPass();
|
||||||
|
co_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DepthPrepass::endFrame()
|
Job DepthPrepass::endFrame()
|
||||||
{
|
{
|
||||||
|
co_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DepthPrepass::publishOutputs()
|
void DepthPrepass::publishOutputs()
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ public:
|
|||||||
DepthPrepassMeshProcessor(Gfx::PViewport viewport, Gfx::PGraphics graphics);
|
DepthPrepassMeshProcessor(Gfx::PViewport viewport, Gfx::PGraphics graphics);
|
||||||
virtual ~DepthPrepassMeshProcessor();
|
virtual ~DepthPrepassMeshProcessor();
|
||||||
|
|
||||||
virtual void addMeshBatch(
|
virtual Job processMeshBatch(
|
||||||
const MeshBatch& batch,
|
const MeshBatch& batch,
|
||||||
const Gfx::PRenderPass renderPass,
|
const Gfx::PRenderPass& renderPass,
|
||||||
Gfx::PPipelineLayout pipelineLayout,
|
Gfx::PPipelineLayout pipelineLayout,
|
||||||
Gfx::PDescriptorLayout primitiveLayout,
|
Gfx::PDescriptorLayout primitiveLayout,
|
||||||
Array<Gfx::PDescriptorSet>& descriptorSets,
|
Array<Gfx::PDescriptorSet> descriptorSets,
|
||||||
int32 staticMeshId = -1) override;
|
int32 staticMeshId = -1) override;
|
||||||
|
|
||||||
Array<Gfx::PRenderCommand> getRenderCommands();
|
Array<Gfx::PRenderCommand> getRenderCommands();
|
||||||
@@ -39,9 +39,9 @@ class DepthPrepass : public RenderPass<DepthPrepassData>
|
|||||||
public:
|
public:
|
||||||
DepthPrepass(Gfx::PGraphics graphics, Gfx::PViewport viewport, PCameraActor source);
|
DepthPrepass(Gfx::PGraphics graphics, Gfx::PViewport viewport, PCameraActor source);
|
||||||
~DepthPrepass();
|
~DepthPrepass();
|
||||||
virtual void beginFrame() override;
|
virtual Job beginFrame() override;
|
||||||
virtual void render() override;
|
virtual Job render() override;
|
||||||
virtual void endFrame() override;
|
virtual Job endFrame() override;
|
||||||
virtual void publishOutputs() override;
|
virtual void publishOutputs() override;
|
||||||
virtual void createRenderPass() override;
|
virtual void createRenderPass() override;
|
||||||
static void modifyRenderPassMacros(Map<const char*, const char*>& defines);
|
static void modifyRenderPassMacros(Map<const char*, const char*>& defines);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ LightCullingPass::~LightCullingPass()
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LightCullingPass::beginFrame()
|
Job LightCullingPass::beginFrame()
|
||||||
{
|
{
|
||||||
uint32_t viewportWidth = viewport->getSizeX();
|
uint32_t viewportWidth = viewport->getSizeX();
|
||||||
uint32_t viewportHeight = viewport->getSizeY();
|
uint32_t viewportHeight = viewport->getSizeY();
|
||||||
@@ -75,9 +75,10 @@ void LightCullingPass::beginFrame()
|
|||||||
lightEnvDescriptorSet->updateBuffer(2, pointLightBuffer);
|
lightEnvDescriptorSet->updateBuffer(2, pointLightBuffer);
|
||||||
lightEnvDescriptorSet->updateBuffer(3, numPointLightBuffer);
|
lightEnvDescriptorSet->updateBuffer(3, numPointLightBuffer);
|
||||||
lightEnvDescriptorSet->writeChanges();
|
lightEnvDescriptorSet->writeChanges();
|
||||||
|
co_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LightCullingPass::render()
|
Job LightCullingPass::render()
|
||||||
{
|
{
|
||||||
oLightIndexList->pipelineBarrier(
|
oLightIndexList->pipelineBarrier(
|
||||||
Gfx::SE_ACCESS_SHADER_READ_BIT, Gfx::SE_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
Gfx::SE_ACCESS_SHADER_READ_BIT, Gfx::SE_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
||||||
@@ -101,11 +102,12 @@ void LightCullingPass::render()
|
|||||||
graphics->executeCommands(commands);
|
graphics->executeCommands(commands);
|
||||||
depthAttachment->changeLayout(Gfx::SE_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
|
depthAttachment->changeLayout(Gfx::SE_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
|
||||||
depthAttachment->transferOwnership(Gfx::QueueType::GRAPHICS);
|
depthAttachment->transferOwnership(Gfx::QueueType::GRAPHICS);
|
||||||
|
co_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LightCullingPass::endFrame()
|
Job LightCullingPass::endFrame()
|
||||||
{
|
{
|
||||||
|
co_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LightCullingPass::publishOutputs()
|
void LightCullingPass::publishOutputs()
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ class LightCullingPass : public RenderPass<LightCullingPassData>
|
|||||||
public:
|
public:
|
||||||
LightCullingPass(Gfx::PGraphics graphics, Gfx::PViewport viewport, PCameraActor camera);
|
LightCullingPass(Gfx::PGraphics graphics, Gfx::PViewport viewport, PCameraActor camera);
|
||||||
virtual ~LightCullingPass();
|
virtual ~LightCullingPass();
|
||||||
virtual void beginFrame() override;
|
virtual Job beginFrame() override;
|
||||||
virtual void render() override;
|
virtual Job render() override;
|
||||||
virtual void endFrame() override;
|
virtual Job endFrame() override;
|
||||||
virtual void publishOutputs() override;
|
virtual void publishOutputs() override;
|
||||||
virtual void createRenderPass() override;
|
virtual void createRenderPass() override;
|
||||||
static void modifyRenderPassMacros(Map<const char*, const char*>& defines);
|
static void modifyRenderPassMacros(Map<const char*, const char*>& defines);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "Scene/Scene.h"
|
#include "Scene/Scene.h"
|
||||||
#include "Graphics/GraphicsResources.h"
|
#include "Graphics/GraphicsResources.h"
|
||||||
#include "Graphics/MeshBatch.h"
|
#include "Graphics/MeshBatch.h"
|
||||||
|
#include "ThreadPool.h"
|
||||||
|
|
||||||
namespace Seele
|
namespace Seele
|
||||||
{
|
{
|
||||||
@@ -14,13 +15,13 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
PScene scene;
|
PScene scene;
|
||||||
Gfx::PGraphics graphics;
|
Gfx::PGraphics graphics;
|
||||||
virtual void addMeshBatch(
|
virtual Job processMeshBatch(
|
||||||
const MeshBatch& meshBatch,
|
const MeshBatch& batch,
|
||||||
// const PPrimitiveComponent primitiveComponent,
|
// const PPrimitiveComponent primitiveComponent,
|
||||||
const Gfx::PRenderPass renderPass,
|
const Gfx::PRenderPass& renderPass,
|
||||||
Gfx::PPipelineLayout pipelineLayout,
|
Gfx::PPipelineLayout pipelineLayout,
|
||||||
Gfx::PDescriptorLayout primitiveLayout,
|
Gfx::PDescriptorLayout primitiveLayout,
|
||||||
Array<Gfx::PDescriptorSet>& descriptorSets,
|
Array<Gfx::PDescriptorSet> descriptorSets,
|
||||||
int32 staticMeshId = -1) = 0;
|
int32 staticMeshId = -1) = 0;
|
||||||
void buildMeshDrawCommand(
|
void buildMeshDrawCommand(
|
||||||
const MeshBatch& meshBatch,
|
const MeshBatch& meshBatch,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "MinimalEngine.h"
|
#include "MinimalEngine.h"
|
||||||
#include "Math/Math.h"
|
#include "Math/Math.h"
|
||||||
#include "RenderGraph.h"
|
#include "RenderGraph.h"
|
||||||
|
#include "ThreadPool.h"
|
||||||
|
|
||||||
namespace Seele
|
namespace Seele
|
||||||
{
|
{
|
||||||
@@ -21,9 +22,9 @@ public:
|
|||||||
void updateViewFrame(RenderPassDataType viewFrame) {
|
void updateViewFrame(RenderPassDataType viewFrame) {
|
||||||
passData = std::move(viewFrame);
|
passData = std::move(viewFrame);
|
||||||
}
|
}
|
||||||
virtual void beginFrame() = 0;
|
virtual Job beginFrame() = 0;
|
||||||
virtual void render() = 0;
|
virtual Job render() = 0;
|
||||||
virtual void endFrame() = 0;
|
virtual Job endFrame() = 0;
|
||||||
virtual void publishOutputs() = 0;
|
virtual void publishOutputs() = 0;
|
||||||
virtual void createRenderPass() = 0;
|
virtual void createRenderPass() = 0;
|
||||||
void setResources(PRenderGraphResources resources) { this->resources = resources; }
|
void setResources(PRenderGraphResources resources) { this->resources = resources; }
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ UIPass::~UIPass()
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UIPass::beginFrame()
|
Job UIPass::beginFrame()
|
||||||
{
|
{
|
||||||
|
co_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UIPass::render()
|
Job UIPass::render()
|
||||||
{
|
{
|
||||||
graphics->beginRenderPass(renderPass);
|
graphics->beginRenderPass(renderPass);
|
||||||
Gfx::PRenderCommand command = graphics->createRenderCommand("UIPassCommand");
|
Gfx::PRenderCommand command = graphics->createRenderCommand("UIPassCommand");
|
||||||
@@ -29,11 +29,12 @@ void UIPass::render()
|
|||||||
command->draw(4, 1, 0, 0);
|
command->draw(4, 1, 0, 0);
|
||||||
graphics->executeCommands(Array<Gfx::PRenderCommand>({command}));
|
graphics->executeCommands(Array<Gfx::PRenderCommand>({command}));
|
||||||
graphics->endRenderPass();
|
graphics->endRenderPass();
|
||||||
|
co_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UIPass::endFrame()
|
Job UIPass::endFrame()
|
||||||
{
|
{
|
||||||
|
co_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UIPass::publishOutputs()
|
void UIPass::publishOutputs()
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ class UIPass : public RenderPass<UIPassData>
|
|||||||
public:
|
public:
|
||||||
UIPass(Gfx::PGraphics graphics, Gfx::PViewport viewport, Gfx::PRenderTargetAttachment renderTarget);
|
UIPass(Gfx::PGraphics graphics, Gfx::PViewport viewport, Gfx::PRenderTargetAttachment renderTarget);
|
||||||
virtual ~UIPass();
|
virtual ~UIPass();
|
||||||
virtual void beginFrame() override;
|
virtual Job beginFrame() override;
|
||||||
virtual void render() override;
|
virtual Job render() override;
|
||||||
virtual void endFrame() override;
|
virtual Job endFrame() override;
|
||||||
virtual void publishOutputs() override;
|
virtual void publishOutputs() override;
|
||||||
virtual void createRenderPass() override;
|
virtual void createRenderPass() override;
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ Actor::~Actor()
|
|||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
void Actor::tick(float deltaTime)
|
void Actor::tick(float)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
void Actor::notifySceneAttach(PScene scene)
|
void Actor::notifySceneAttach(PScene scene)
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ Scene::~Scene()
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scene::tick(double deltaTime)
|
void Scene::tick(double)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+26
-17
@@ -14,7 +14,7 @@ Event::Event(const std::string& name)
|
|||||||
void Event::raise()
|
void Event::raise()
|
||||||
{
|
{
|
||||||
flag->store(1);
|
flag->store(1);
|
||||||
getGlobalThreadPool().notify(this);
|
getGlobalThreadPool().notify(*this);
|
||||||
}
|
}
|
||||||
void Event::reset()
|
void Event::reset()
|
||||||
{
|
{
|
||||||
@@ -47,51 +47,56 @@ ThreadPool::~ThreadPool()
|
|||||||
void ThreadPool::addJob(Job&& job)
|
void ThreadPool::addJob(Job&& job)
|
||||||
{
|
{
|
||||||
std::unique_lock lock(jobQueueLock);
|
std::unique_lock lock(jobQueueLock);
|
||||||
std::cout << "Adding job " << job.id << std::endl;
|
//std::cout << "Adding job " << job.id << std::endl;
|
||||||
jobQueue.add(std::move(job));
|
jobQueue.add(std::move(job));
|
||||||
jobQueueCV.notify_one();
|
jobQueueCV.notify_one();
|
||||||
}
|
}
|
||||||
void ThreadPool::addJob(MainJob&& job)
|
void ThreadPool::addJob(MainJob&& job)
|
||||||
{
|
{
|
||||||
std::unique_lock lock(mainJobLock);
|
std::unique_lock lock(mainJobLock);
|
||||||
std::cout << "Adding main job " << job.id << std::endl;
|
//std::cout << "Adding main job " << job.id << std::endl;
|
||||||
mainJobs.add(std::move(job));
|
mainJobs.add(std::move(job));
|
||||||
mainJobCV.notify_one();
|
mainJobCV.notify_one();
|
||||||
}
|
}
|
||||||
void ThreadPool::enqueueWaiting(Event* event, Job&& job)
|
void ThreadPool::enqueueWaiting(Event& event, Job job)
|
||||||
{
|
{
|
||||||
std::cout << job.id << " waiting for event " << event->name << std::endl;
|
//std::cout << job.id << " waiting for event " << event.name << std::endl;
|
||||||
std::unique_lock lock(waitingLock);
|
std::unique_lock lock(waitingLock);
|
||||||
waitingJobs[*event].add(std::move(job));
|
waitingJobs[event].add(std::move(job));
|
||||||
}
|
}
|
||||||
void ThreadPool::enqueueWaiting(Event* event, MainJob&& job)
|
void ThreadPool::enqueueWaiting(Event& event, MainJob job)
|
||||||
{
|
{
|
||||||
std::cout << job.id << " main waiting for event " << event->name << std::endl;
|
//std::cout << job.id << " main waiting for event " << event.name << std::endl;
|
||||||
std::unique_lock lock(waitingMainLock);
|
std::unique_lock lock(waitingMainLock);
|
||||||
waitingMainJobs[*event].add(std::move(job));
|
waitingMainJobs[event].add(std::move(job));
|
||||||
}
|
}
|
||||||
void ThreadPool::notify(Event* event)
|
void ThreadPool::notify(Event& event)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
std::unique_lock lock(jobQueueLock);
|
std::unique_lock lock(jobQueueLock);
|
||||||
std::unique_lock lock2(waitingLock);
|
std::unique_lock lock2(waitingLock);
|
||||||
while(!waitingJobs[*event].empty())
|
List<Job>& jobs = waitingJobs[event];
|
||||||
|
for(auto& job : jobs)
|
||||||
{
|
{
|
||||||
std::cout << "Waking up job " << waitingJobs[*event].front().id << std::endl;
|
//assert(job.id != -1ull);
|
||||||
Job job = std::move(waitingJobs[*event].retrieve());
|
//std::cout << "Waking up job " << job.id << std::endl;
|
||||||
jobQueue.add(std::move(job));
|
jobQueue.add(std::move(job));
|
||||||
jobQueueCV.notify_one();
|
jobQueueCV.notify_one();
|
||||||
}
|
}
|
||||||
|
jobs.clear();
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
std::unique_lock lock(mainJobLock);
|
std::unique_lock lock(mainJobLock);
|
||||||
std::unique_lock lock2(waitingMainLock);
|
std::unique_lock lock2(waitingMainLock);
|
||||||
while(!waitingMainJobs[*event].empty())
|
List<MainJob>& jobs = waitingMainJobs[event];
|
||||||
|
for(auto& job : jobs)
|
||||||
{
|
{
|
||||||
std::cout << "Waking up main job " << waitingMainJobs[*event].front().id << std::endl;
|
//assert(job.id != -1ull);
|
||||||
mainJobs.add(std::move(waitingMainJobs[*event].retrieve()));
|
//std::cout << "Waking up main job " << job.id << std::endl;
|
||||||
|
mainJobs.add(std::move(job));
|
||||||
mainJobCV.notify_one();
|
mainJobCV.notify_one();
|
||||||
}
|
}
|
||||||
|
jobs.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void ThreadPool::tryMainJob()
|
void ThreadPool::tryMainJob()
|
||||||
@@ -134,8 +139,12 @@ void ThreadPool::threadLoop(const bool mainThread)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::cout << "Starting job " << job.id << std::endl;
|
//std::cout << "Starting job " << job.id << std::endl;
|
||||||
job.resume();
|
job.resume();
|
||||||
|
if(job.done())
|
||||||
|
{
|
||||||
|
job.signal();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+61
-68
@@ -25,69 +25,6 @@ struct JobPromiseBase
|
|||||||
exit(1);
|
exit(1);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
static std::atomic_uint64_t globalCounter;
|
|
||||||
template<bool MainJob>
|
|
||||||
struct JobBase
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using promise_type = JobPromiseBase<MainJob>;
|
|
||||||
|
|
||||||
explicit JobBase()
|
|
||||||
: id(-1)
|
|
||||||
{}
|
|
||||||
explicit JobBase(std::coroutine_handle<promise_type> handle)
|
|
||||||
: handle(handle)
|
|
||||||
, id(globalCounter++)
|
|
||||||
{
|
|
||||||
std::cout << "Creating job " << id << std::endl;
|
|
||||||
/*if constexpr(MainJob)
|
|
||||||
{
|
|
||||||
std::cout << "Creating mainjob " << handle.address() << std::endl;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::cout << "Creating job " << handle.address() << std::endl;
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
JobBase(const JobBase & rhs) = delete;
|
|
||||||
JobBase(JobBase&& rhs)
|
|
||||||
: handle(std::move(rhs.handle))
|
|
||||||
, id(std::move(rhs.id))
|
|
||||||
{
|
|
||||||
rhs.id = -1;
|
|
||||||
rhs.handle = nullptr;
|
|
||||||
}
|
|
||||||
~JobBase()
|
|
||||||
{
|
|
||||||
if(handle)
|
|
||||||
{
|
|
||||||
std::cout << "Destroying job " << id << std::endl;
|
|
||||||
handle.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
JobBase& operator=(const JobBase& rhs) = delete;
|
|
||||||
JobBase& operator=(JobBase&& rhs)
|
|
||||||
{
|
|
||||||
if(this != &rhs)
|
|
||||||
{
|
|
||||||
handle = std::move(rhs.handle);
|
|
||||||
id = std::move(rhs.id);
|
|
||||||
rhs.id = -1;
|
|
||||||
rhs.handle = nullptr;
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
void resume()
|
|
||||||
{
|
|
||||||
handle.resume();
|
|
||||||
}
|
|
||||||
std::coroutine_handle<promise_type> handle;
|
|
||||||
uint64 id;
|
|
||||||
private:
|
|
||||||
};
|
|
||||||
|
|
||||||
using MainJob = JobBase<true>;
|
|
||||||
using Job = JobBase<false>;
|
|
||||||
|
|
||||||
struct Event
|
struct Event
|
||||||
{
|
{
|
||||||
@@ -119,16 +56,72 @@ private:
|
|||||||
friend class ThreadPool;
|
friend class ThreadPool;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static std::atomic_uint64_t globalCounter;
|
||||||
|
template<bool MainJob>
|
||||||
|
struct JobBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using promise_type = JobPromiseBase<MainJob>;
|
||||||
|
|
||||||
|
explicit JobBase()
|
||||||
|
: id(-1)
|
||||||
|
{}
|
||||||
|
explicit JobBase(std::coroutine_handle<promise_type> handle)
|
||||||
|
: handle(handle)
|
||||||
|
, id(globalCounter++)
|
||||||
|
{
|
||||||
|
//std::cout << "Creating job " << id << std::endl;
|
||||||
|
}
|
||||||
|
JobBase(const JobBase & rhs) = delete;
|
||||||
|
JobBase(JobBase&& rhs)
|
||||||
|
: handle(std::move(rhs.handle))
|
||||||
|
, id(std::move(rhs.id))
|
||||||
|
{
|
||||||
|
rhs.id = -1;
|
||||||
|
rhs.handle = nullptr;
|
||||||
|
}
|
||||||
|
~JobBase()
|
||||||
|
{
|
||||||
|
if(handle && handle.done())
|
||||||
|
{
|
||||||
|
//std::cout << "Destroying job " << id << std::endl;
|
||||||
|
handle.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JobBase& operator=(const JobBase& rhs) = delete;
|
||||||
|
JobBase& operator=(JobBase&& rhs)
|
||||||
|
{
|
||||||
|
if(this != &rhs)
|
||||||
|
{
|
||||||
|
handle = std::move(rhs.handle);
|
||||||
|
id = std::move(rhs.id);
|
||||||
|
rhs.id = -1;
|
||||||
|
rhs.handle = nullptr;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
void resume()
|
||||||
|
{
|
||||||
|
handle.resume();
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
std::coroutine_handle<promise_type> handle;
|
||||||
|
uint64 id;
|
||||||
|
};
|
||||||
|
|
||||||
|
using MainJob = JobBase<true>;
|
||||||
|
using Job = JobBase<false>;
|
||||||
|
|
||||||
class ThreadPool
|
class ThreadPool
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ThreadPool(uint32 threadCount = std::thread::hardware_concurrency());
|
ThreadPool(uint32 threadCount = std::thread::hardware_concurrency() + 1);
|
||||||
virtual ~ThreadPool();
|
virtual ~ThreadPool();
|
||||||
void addJob(Job&& job);
|
void addJob(Job&& job);
|
||||||
void addJob(MainJob&& job);
|
void addJob(MainJob&& job);
|
||||||
void enqueueWaiting(Event* event, Job&& job);
|
void enqueueWaiting(Event& event, Job job);
|
||||||
void enqueueWaiting(Event* event, MainJob&& job);
|
void enqueueWaiting(Event& event, MainJob job);
|
||||||
void notify(Event* event);
|
void notify(Event& event);
|
||||||
private:
|
private:
|
||||||
std::atomic_bool running;
|
std::atomic_bool running;
|
||||||
std::thread* mainThread;
|
std::thread* mainThread;
|
||||||
@@ -176,7 +169,7 @@ inline auto JobPromiseBase<MainJob>::initial_suspend() const noexcept
|
|||||||
template<bool MainJob>
|
template<bool MainJob>
|
||||||
inline constexpr void Event::await_suspend(std::coroutine_handle<JobPromiseBase<MainJob>> h)
|
inline constexpr void Event::await_suspend(std::coroutine_handle<JobPromiseBase<MainJob>> h)
|
||||||
{
|
{
|
||||||
getGlobalThreadPool().enqueueWaiting(this, std::move(JobBase<MainJob>(h)));
|
getGlobalThreadPool().enqueueWaiting(*this, std::move(JobBase<MainJob>(h)));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Seele
|
} // namespace Seele
|
||||||
|
|||||||
@@ -34,31 +34,36 @@ void InspectorView::prepareRender()
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InspectorView::render()
|
Job InspectorView::render()
|
||||||
{
|
{
|
||||||
|
co_await uiPass.beginFrame();
|
||||||
|
co_await uiPass.render();
|
||||||
|
co_await uiPass.endFrame();
|
||||||
|
|
||||||
|
renderFinishedEvent.raise();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InspectorView::keyCallback(KeyCode code, InputAction action, KeyModifier modifier)
|
void InspectorView::keyCallback(KeyCode, InputAction, KeyModifier)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InspectorView::mouseMoveCallback(double xPos, double yPos)
|
void InspectorView::mouseMoveCallback(double, double)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InspectorView::mouseButtonCallback(MouseButton button, InputAction action, KeyModifier modifier)
|
void InspectorView::mouseButtonCallback(MouseButton, InputAction, KeyModifier)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InspectorView::scrollCallback(double xOffset, double yOffset)
|
void InspectorView::scrollCallback(double, double)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InspectorView::fileCallback(int count, const char** paths)
|
void InspectorView::fileCallback(int, const char**)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public:
|
|||||||
virtual void commitUpdate() override;
|
virtual void commitUpdate() override;
|
||||||
|
|
||||||
virtual void prepareRender() override;
|
virtual void prepareRender() override;
|
||||||
virtual void render() override;
|
virtual Job render() override;
|
||||||
void selectActor();
|
void selectActor();
|
||||||
protected:
|
protected:
|
||||||
UIPass uiPass;
|
UIPass uiPass;
|
||||||
|
|||||||
@@ -83,19 +83,21 @@ void SceneView::prepareRender()
|
|||||||
basePass.updateViewFrame(basePassData);
|
basePass.updateViewFrame(basePassData);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneView::render()
|
Job SceneView::render()
|
||||||
{
|
{
|
||||||
depthPrepass.beginFrame();
|
co_await depthPrepass.beginFrame();
|
||||||
lightCullingPass.beginFrame();
|
co_await lightCullingPass.beginFrame();
|
||||||
basePass.beginFrame();
|
co_await basePass.beginFrame();
|
||||||
|
|
||||||
depthPrepass.render();
|
co_await depthPrepass.render();
|
||||||
lightCullingPass.render();
|
co_await lightCullingPass.render();
|
||||||
basePass.render();
|
co_await basePass.render();
|
||||||
|
|
||||||
depthPrepass.endFrame();
|
co_await depthPrepass.endFrame();
|
||||||
lightCullingPass.endFrame();
|
co_await lightCullingPass.endFrame();
|
||||||
basePass.endFrame();
|
co_await basePass.endFrame();
|
||||||
|
|
||||||
|
renderFinishedEvent.raise();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneView::keyCallback(KeyCode code, InputAction action, KeyModifier)
|
void SceneView::keyCallback(KeyCode code, InputAction action, KeyModifier)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public:
|
|||||||
virtual void commitUpdate() override;
|
virtual void commitUpdate() override;
|
||||||
|
|
||||||
virtual void prepareRender() override;
|
virtual void prepareRender() override;
|
||||||
virtual void render() override;
|
virtual Job render() override;
|
||||||
|
|
||||||
PScene getScene() const { return scene; }
|
PScene getScene() const { return scene; }
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "Graphics/RenderPass/RenderGraph.h"
|
#include "Graphics/RenderPass/RenderGraph.h"
|
||||||
|
#include "ThreadPool.h"
|
||||||
|
|
||||||
namespace Seele
|
namespace Seele
|
||||||
{
|
{
|
||||||
@@ -20,15 +21,17 @@ public:
|
|||||||
// These are called from the render thread
|
// These are called from the render thread
|
||||||
// prepare render is also locked, so reading from shared memory is also safe
|
// prepare render is also locked, so reading from shared memory is also safe
|
||||||
virtual void prepareRender() {}
|
virtual void prepareRender() {}
|
||||||
virtual void render() {}
|
virtual Job render() { co_return; }
|
||||||
void applyArea(URect area);
|
void applyArea(URect area);
|
||||||
void setFocused();
|
void setFocused();
|
||||||
|
Event renderFinished() { return renderFinishedEvent; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Gfx::PGraphics graphics;
|
Gfx::PGraphics graphics;
|
||||||
Gfx::PViewport viewport;
|
Gfx::PViewport viewport;
|
||||||
PWindow owner;
|
PWindow owner;
|
||||||
std::string name;
|
std::string name;
|
||||||
|
Event renderFinishedEvent;
|
||||||
|
|
||||||
virtual void keyCallback(KeyCode code, InputAction action, KeyModifier modifier) = 0;
|
virtual void keyCallback(KeyCode code, InputAction action, KeyModifier modifier) = 0;
|
||||||
virtual void mouseMoveCallback(double xPos, double yPos) = 0;
|
virtual void mouseMoveCallback(double xPos, double yPos) = 0;
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ MainJob Window::render()
|
|||||||
}
|
}
|
||||||
windowView->view->render();
|
windowView->view->render();
|
||||||
}
|
}
|
||||||
|
for(auto& windowView : views)
|
||||||
|
{
|
||||||
|
co_await windowView->view->renderFinished();
|
||||||
|
}
|
||||||
gfxHandle->endFrame();
|
gfxHandle->endFrame();
|
||||||
//Enqueue a new render main job
|
//Enqueue a new render main job
|
||||||
render();
|
render();
|
||||||
|
|||||||
Reference in New Issue
Block a user