From fd8dc5ed0f9ef4d979d7b2f89229413ef210822b Mon Sep 17 00:00:00 2001 From: Dynamitos Date: Fri, 5 Jul 2024 12:02:46 +0200 Subject: [PATCH] Adding benchmark --- CMakeLists.txt | 4 + allcull.png | Bin 0 -> 54514 bytes allnocull.png | Bin 0 -> 48172 bytes benchmark.py | 4 + plots.py | 62 +++ src/Benchmark/CMakeLists.txt | 5 + src/Benchmark/PlayView.cpp | 63 +++ src/Benchmark/PlayView.h | 22 + src/Benchmark/main.cpp | 60 +++ src/CMakeLists.txt | 1 + src/Editor/Asset/TextureLoader.cpp | 183 ++++--- src/Editor/Window/PlayView.cpp | 28 ++ src/Editor/Window/PlayView.h | 2 + src/Editor/main.cpp | 111 +++-- src/Engine/Asset/TextureAsset.cpp | 36 +- src/Engine/Asset/TextureAsset.h | 4 +- src/Engine/Graphics/Query.h | 2 +- src/Engine/Graphics/RenderPass/BasePass.cpp | 469 ++++++++++++++---- src/Engine/Graphics/RenderPass/BasePass.h | 30 ++ src/Engine/Graphics/RenderPass/CMakeLists.txt | 6 - .../Graphics/RenderPass/CachedDepthPass.cpp | 19 +- src/Engine/Graphics/RenderPass/DebugPass.cpp | 156 ------ src/Engine/Graphics/RenderPass/DebugPass.h | 35 -- .../Graphics/RenderPass/DepthCullingPass.cpp | 23 +- .../Graphics/RenderPass/LightCullingPass.cpp | 33 +- .../Graphics/RenderPass/LightCullingPass.h | 1 - src/Engine/Graphics/RenderPass/RenderPass.cpp | 4 +- .../Graphics/RenderPass/SkyboxRenderPass.cpp | 176 ------- .../Graphics/RenderPass/SkyboxRenderPass.h | 41 -- .../Graphics/RenderPass/VisibilityPass.cpp | 2 - src/Engine/Graphics/Resources.h | 5 - src/Engine/Graphics/VertexData.cpp | 319 ++++++------ src/Engine/Graphics/Vulkan/Buffer.cpp | 16 +- src/Engine/Graphics/Vulkan/Command.cpp | 1 - src/Engine/Graphics/Vulkan/Graphics.cpp | 9 +- src/Engine/Graphics/Vulkan/Query.cpp | 7 +- src/Engine/Graphics/Vulkan/RenderPass.cpp | 24 +- src/Engine/Graphics/Vulkan/Texture.cpp | 13 +- src/Engine/Graphics/Vulkan/Window.cpp | 7 +- src/Engine/Math/Vector.cpp | 11 +- src/Engine/Math/Vector.h | 1 + src/Engine/MinimalEngine.h | 9 + src/Engine/Scene/LightEnvironment.cpp | 11 +- src/Engine/ThreadPool.cpp | 5 + src/Engine/ThreadPool.h | 1 - src/Engine/Window/GameView.cpp | 59 +-- src/Engine/Window/GameView.h | 1 - 47 files changed, 1071 insertions(+), 1010 deletions(-) create mode 100644 allcull.png create mode 100644 allnocull.png create mode 100644 benchmark.py create mode 100644 plots.py create mode 100644 src/Benchmark/CMakeLists.txt create mode 100644 src/Benchmark/PlayView.cpp create mode 100644 src/Benchmark/PlayView.h create mode 100644 src/Benchmark/main.cpp delete mode 100644 src/Engine/Graphics/RenderPass/DebugPass.cpp delete mode 100644 src/Engine/Graphics/RenderPass/DebugPass.h delete mode 100644 src/Engine/Graphics/RenderPass/SkyboxRenderPass.cpp delete mode 100644 src/Engine/Graphics/RenderPass/SkyboxRenderPass.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 435fc77..df22608 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,6 +96,10 @@ add_executable(AssetViewer "") target_link_libraries(AssetViewer PRIVATE Engine) target_include_directories(AssetViewer PRIVATE src/AssetViewer) +add_executable(Benchmark "") +target_link_libraries(Benchmark PRIVATE Engine) +target_include_directories(Benchmark PRIVATE src/Benchmark) + if(MSVC) add_definitions(-D_CRT_SECURE_NO_WARNINGS) target_compile_options(Engine PUBLIC /std:c++20 /Zi /MP14 /W4 /wd4505) diff --git a/allcull.png b/allcull.png new file mode 100644 index 0000000000000000000000000000000000000000..d650afeee35f546888a31982ce1ffa6eb75b7a23 GIT binary patch literal 54514 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 literal 0 HcmV?d00001 diff --git a/allnocull.png b/allnocull.png new file mode 100644 index 0000000000000000000000000000000000000000..d4a6b53329f7c0fa9f1c23d1c32cb7a7a6cec384 GIT binary patch literal 48172 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 MhNe>SHS^H_1=As$yZ`_I literal 0 HcmV?d00001 diff --git a/benchmark.py b/benchmark.py new file mode 100644 index 0000000..5ec632e --- /dev/null +++ b/benchmark.py @@ -0,0 +1,4 @@ +import subprocess + +subprocess.run(['build/Benchmark.exe'], cwd='build') +subprocess.run(['build/Benchmark.exe', 'NOCULL'], cwd='build') \ No newline at end of file diff --git a/plots.py b/plots.py new file mode 100644 index 0000000..12e7695 --- /dev/null +++ b/plots.py @@ -0,0 +1,62 @@ +import pandas as pd +import matplotlib.pyplot as plt + +df = pd.read_csv('build/stats.csv') +noculldf = pd.read_csv('build/statsNOCULL.csv') + +fig, ax = plt.subplots() + +scaled_reltime = df['RelTime'] / 10**9 + +scaled_FrameTime = df['FrameTime'].rolling(window=100).mean() / 10**9 +scaled_CACHED = df['CACHED'].rolling(window=100).mean() / 10**9 +scaled_MIPGEN = df['MIPGEN'].rolling(window=100).mean() / 10**9 +scaled_DEPTHCULL = df['DEPTHCULL'].rolling(window=100).mean() / 10**9 +scaled_VISIBILITY = df['VISIBILITY'].rolling(window=100).mean() / 10**9 +scaled_LIGHTCULL = df['LIGHTCULL'].rolling(window=100).mean() / 10**9 +scaled_BASE = df['BASE'].rolling(window=100).mean() / 10**9 + +ax.plot(scaled_reltime, scaled_FrameTime, label='Frame Time') +ax.plot(scaled_reltime, scaled_CACHED, label='CACHED') +ax.plot(scaled_reltime, scaled_MIPGEN, label='MIPGEN') +ax.plot(scaled_reltime, scaled_DEPTHCULL, label='DEPTHCULL') +ax.plot(scaled_reltime, scaled_VISIBILITY, label='VISIBILITY') +ax.plot(scaled_reltime, scaled_LIGHTCULL, label='LIGHTCULL') +ax.plot(scaled_reltime, scaled_BASE, label='BASE') + + + +ax.set_xlabel('Application Time (ms)') +ax.set_ylabel('Render Times (ms)') + +ax.legend() + +fig.savefig('allcull.png') + +nocullfig, nocullax = plt.subplots() + +nocullscaled_reltime = noculldf['RelTime'] / 10**9 + +nocullscaled_FrameTime = noculldf['FrameTime'].rolling(window=100).mean() / 10**9 +nocullscaled_CACHED = noculldf['CACHED'].rolling(window=100).mean() / 10**9 +nocullscaled_MIPGEN = noculldf['MIPGEN'].rolling(window=100).mean() / 10**9 +nocullscaled_DEPTHCULL = noculldf['DEPTHCULL'].rolling(window=100).mean() / 10**9 +nocullscaled_VISIBILITY = noculldf['VISIBILITY'].rolling(window=100).mean() / 10**9 +nocullscaled_LIGHTCULL = noculldf['LIGHTCULL'].rolling(window=100).mean() / 10**9 +nocullscaled_BASE = noculldf['BASE'].rolling(window=100).mean() / 10**9 + +nocullax.plot(nocullscaled_reltime, nocullscaled_FrameTime, label='Frame Time') +nocullax.plot(nocullscaled_reltime, nocullscaled_CACHED, label='CACHED') +nocullax.plot(nocullscaled_reltime, nocullscaled_MIPGEN, label='MIPGEN') +nocullax.plot(nocullscaled_reltime, nocullscaled_DEPTHCULL, label='DEPTHCULL') +nocullax.plot(nocullscaled_reltime, nocullscaled_VISIBILITY, label='VISIBILITY') +nocullax.plot(nocullscaled_reltime, nocullscaled_LIGHTCULL, label='LIGHTCULL') +nocullax.plot(nocullscaled_reltime, nocullscaled_BASE, label='BASE') + +nocullax.set_xlabel('Application Time (ms)') +nocullax.set_ylabel('Render Times (ms)') + +nocullax.legend() + +nocullfig.savefig('allnocull.png') + diff --git a/src/Benchmark/CMakeLists.txt b/src/Benchmark/CMakeLists.txt new file mode 100644 index 0000000..038a7ae --- /dev/null +++ b/src/Benchmark/CMakeLists.txt @@ -0,0 +1,5 @@ +target_sources(Benchmark + PUBLIC + main.cpp + PlayView.h + PlayView.cpp) \ No newline at end of file diff --git a/src/Benchmark/PlayView.cpp b/src/Benchmark/PlayView.cpp new file mode 100644 index 0000000..f0a14e1 --- /dev/null +++ b/src/Benchmark/PlayView.cpp @@ -0,0 +1,63 @@ +#include "PlayView.h" +#include "Window/Window.h" +#include +#include + +using namespace Seele; + +PlayView::PlayView(Gfx::PGraphics graphics, PWindow window, const ViewportCreateInfo& createInfo, std::string dllPath, bool useMeshCulling) + : GameView(graphics, window, createInfo, dllPath) { + getGlobals().useDepthCulling = useMeshCulling; + queryThread = std::thread([&]() { + PRenderGraphResources res = renderGraph.getResources(); + Gfx::PPipelineStatisticsQuery cachedQuery = res->requestQuery("CACHED_QUERY"); + Gfx::PPipelineStatisticsQuery depthQuery = res->requestQuery("DEPTH_QUERY"); + Gfx::PPipelineStatisticsQuery baseQuery = res->requestQuery("BASEPASS_QUERY"); + Gfx::PPipelineStatisticsQuery lightCullQuery = res->requestQuery("LIGHTCULL_QUERY"); + Gfx::PPipelineStatisticsQuery visibilityQuery = res->requestQuery("VISIBILITY_QUERY"); + Gfx::PTimestampQuery timeQuery = res->requestTimestampQuery("TIMESTAMP"); + std::ofstream stats(fmt::format("stats{}.csv", useMeshCulling ? "" : "NOCULL")); + stats << std::fixed << std::setprecision(0); + stats << "RelTime," + << "CachedIAV,CachedIAP,CachedVS,CachedClipInv,CachedClipPrim,CachedFS,CachedCS,CachedTS,CachedMS," + << "DepthIAV,DepthIAP,DepthVS,DepthClipInv,DepthClipPrim,DepthFS,DepthCS,DepthTS,DepthMS," + << "BaseIAV,BaseIAP,BaseVS,BaseClipInv,BaseClipPrim,BaseFS,BaseCS,BaseTS,BaseMS," + << "LightCullIAV,LightCullIAP,LightCullVS,LightCullClipInv,LightCullClipPrim,LightCullFS,LightCullCS,LightCullTS,LightCullMS," + << "VisibilityIAV,VisibilityIAP,VisibilityVS,VisibilityClipInv,VisibilityClipPrim,VisibilityFS,VisibilityCS,VisibilityTS," + "VisibilityMS," + << "CACHED,MIPGEN,DEPTHCULL,VISIBILITY,LIGHTCULL,BASE,FrameTime" << std::endl; + float start = 0; + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + while (getGlobals().running) { + auto timestamps = timeQuery->getResults(); + auto cachedResults = cachedQuery->getResults(); + auto depthResults = depthQuery->getResults(); + auto baseResults = baseQuery->getResults(); + auto lightCullResults = lightCullQuery->getResults(); + auto visiblityResults = visibilityQuery->getResults(); + if (start == 0) { + start = timestamps[0].time; + } + stats << timestamps[0].time - start << "," << cachedResults << depthResults << baseResults << lightCullResults + << visiblityResults << timestamps[1].time - timestamps[0].time << "," << timestamps[2].time - timestamps[1].time << "," + << timestamps[3].time - timestamps[2].time << "," << timestamps[4].time - timestamps[3].time << "," + << timestamps[5].time - timestamps[4].time << "," << timestamps[6].time - timestamps[5].time << "," + << timestamps[6].time - timestamps[0].time << std::endl; + stats.flush(); + } + }); +} + +PlayView::~PlayView() {} + +void PlayView::beginUpdate() { GameView::beginUpdate(); } + +void PlayView::update() { GameView::update(); } + +void PlayView::commitUpdate() { GameView::commitUpdate(); } + +void PlayView::prepareRender() { GameView::prepareRender(); } + +void PlayView::render() { GameView::render(); } + +void PlayView::keyCallback(KeyCode code, InputAction action, KeyModifier modifier) { GameView::keyCallback(code, action, modifier); } diff --git a/src/Benchmark/PlayView.h b/src/Benchmark/PlayView.h new file mode 100644 index 0000000..08f3acd --- /dev/null +++ b/src/Benchmark/PlayView.h @@ -0,0 +1,22 @@ +#pragma once +#include "Window/GameView.h" + +namespace Seele { +class PlayView : public GameView { + public: + PlayView(Gfx::PGraphics graphics, PWindow window, const ViewportCreateInfo& createInfo, std::string dllPath, bool useMeshCulling); + virtual ~PlayView(); + virtual void beginUpdate() override; + virtual void update() override; + virtual void commitUpdate() override; + + virtual void prepareRender() override; + virtual void render() override; + + virtual void keyCallback(Seele::KeyCode code, Seele::InputAction action, Seele::KeyModifier modifier) override; + + private: + std::thread queryThread; +}; +DECLARE_REF(PlayView) +} // namespace Seele \ No newline at end of file diff --git a/src/Benchmark/main.cpp b/src/Benchmark/main.cpp new file mode 100644 index 0000000..75a71ec --- /dev/null +++ b/src/Benchmark/main.cpp @@ -0,0 +1,60 @@ +#include "Asset/AssetRegistry.h" +#include "Graphics/Initializer.h" +#include "Graphics/StaticMeshVertexData.h" +#include "Graphics/Vulkan/Graphics.h" +#include "PlayView.h" +#include "Window/WindowManager.h" +#include +#include + +using namespace Seele; + +// make it global so it gets deleted last and automatically +static Gfx::OGraphics graphics; + +int main(int argc, char** argv) { + if (argc > 2) + { + return -1; + } + bool useDepthCulling = true; + if (argc == 2 && strcmp(argv[1], "NOCULL") == 0) + { + useDepthCulling = false; + } + + std::filesystem::path binaryPath = "MeshShadingDemo.dll"; + graphics = new Vulkan::Graphics(); + GraphicsInitializer initializer; + graphics->init(initializer); + StaticMeshVertexData* vd = StaticMeshVertexData::getInstance(); + vd->init(graphics); + + OWindowManager windowManager = new WindowManager(); + AssetRegistry::init("Assets", graphics); + vd->commitMeshes(); + WindowCreateInfo mainWindowInfo = { + .width = 1920, + .height = 1080, + .title = "Benchmark", + .preferredFormat = Gfx::SE_FORMAT_B8G8R8A8_SRGB, + }; + auto window = windowManager->addWindow(graphics, mainWindowInfo); + ViewportCreateInfo sceneViewInfo = { + .dimensions = + { + .size = {1920, 1080}, + .offset = {0, 0}, + }, + .numSamples = Gfx::SE_SAMPLE_COUNT_4_BIT, + }; + OGameView sceneView = new PlayView(graphics, window, sceneViewInfo, binaryPath.generic_string(), useDepthCulling); + sceneView->setFocused(); + + while (windowManager->isActive()) { + windowManager->render(); + } + vd->destroy(); + + return 0; +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7d3df86..0a63392 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,3 +1,4 @@ add_subdirectory(AssetViewer/) +add_subdirectory(Benchmark/) add_subdirectory(Editor/) add_subdirectory(Engine/) \ No newline at end of file diff --git a/src/Editor/Asset/TextureLoader.cpp b/src/Editor/Asset/TextureLoader.cpp index 6ceca0c..27a2fbf 100644 --- a/src/Editor/Asset/TextureLoader.cpp +++ b/src/Editor/Asset/TextureLoader.cpp @@ -56,119 +56,104 @@ PTextureAsset TextureLoader::getPlaceholderTexture() { return placeholderAsset; } void TextureLoader::import(TextureImportArgs args, PTextureAsset textureAsset) { - // manually transcode ktx textures using toktx - if (args.filePath.extension().compare("ktx") != 0) { - auto ktxFile = args.filePath; - ktxFile.replace_extension("ktx"); - std::stringstream ss; - ss << "toktx --encode etc1s "; - if (args.type == TextureImportType::TEXTURE_NORMAL) { - // ss << "--normal_mode "; - } - if (args.type == TextureImportType::TEXTURE_CUBEMAP) { - - } - ss << ktxFile << " " << args.filePath; - system(ss.str().c_str()); - args.filePath = ktxFile; + int totalWidth = 0, totalHeight = 0, n = 0; + unsigned char* data = stbi_load(args.filePath.string().c_str(), &totalWidth, &totalHeight, &n, 0); + ktxTexture2* kTexture = nullptr; + VkFormat format; + switch (n) { + case 1: + format = VK_FORMAT_R8_UNORM; + break; + case 2: + format = VK_FORMAT_R8G8_UNORM; + break; + case 3: + format = VK_FORMAT_R8G8B8_UNORM; + break; + case 4: + format = VK_FORMAT_R8G8B8A8_UNORM; + break; } + ktxTextureCreateInfo createInfo = { + .vkFormat = (uint32)format, + .baseDepth = 1, + .numLevels = 1, + .numLayers = 1, + .isArray = false, + .generateMipmaps = false, + }; - ktxTexture2* ktxHandle; - KTX_ASSERT(ktxTexture_CreateFromNamedFile(args.filePath.string().c_str(), 0, (ktxTexture**)&ktxHandle)); + if (args.type == TextureImportType::TEXTURE_CUBEMAP) { + uint32 faceWidth = totalWidth / 4; + // uint32 faceHeight = totalHeight / 3; + // Cube map + createInfo.baseWidth = totalWidth / 4; + createInfo.baseHeight = totalHeight / 3; + createInfo.numFaces = 6; + createInfo.numDimensions = 2; - // int totalWidth = 0, totalHeight = 0, n = 0; - // unsigned char* data = stbi_load(args.filePath.string().c_str(), &totalWidth, &totalHeight, &n, 4); - // ktxTexture2* kTexture = nullptr; - // VkFormat format = VK_FORMAT_R8G8B8A8_UNORM; - // ktxTextureCreateInfo createInfo = { - // .vkFormat = (uint32)format, - // .baseDepth = 1, - // .numLevels = 1, - // .numLayers = 1, - // .isArray = false, - // .generateMipmaps = false, - // }; - // - // if (args.type == TextureImportType::TEXTURE_CUBEMAP) - //{ - // uint32 faceWidth = totalWidth / 4; - // // uint32 faceHeight = totalHeight / 3; - // // Cube map - // createInfo.baseWidth = totalWidth / 4; - // createInfo.baseHeight = totalHeight / 3; - // createInfo.numFaces = 6; - // createInfo.numDimensions = 2; + KTX_ASSERT(ktxTexture2_Create(&createInfo, KTX_TEXTURE_CREATE_ALLOC_STORAGE, &kTexture)); - // KTX_ASSERT(ktxTexture2_Create(&createInfo, - // KTX_TEXTURE_CREATE_ALLOC_STORAGE, - // &kTexture)); + auto loadCubeFace = [n, &kTexture, faceWidth, totalWidth, &data](int xPos, int yPos, int faceName) { + std::vector vec(faceWidth * faceWidth * n); + for (uint32 y = 0; y < faceWidth; ++y) { + for (uint32 x = 0; x < faceWidth; ++x) { + int imgX = x + (xPos * faceWidth); + int imgY = y + (yPos * faceWidth); + std::memcpy(&vec[(x + (faceWidth * y)) * n], &data[(imgX + (totalWidth * imgY)) * n], n); + } + } + ktxTexture_SetImageFromMemory(ktxTexture(kTexture), 0, 0, faceName, vec.data(), vec.size()); + }; + loadCubeFace(2, 1, 0); // +X + loadCubeFace(0, 1, 1); // -X + loadCubeFace(1, 0, 2); // +Y + loadCubeFace(1, 2, 3); // -Y + loadCubeFace(1, 1, 4); // +Z + loadCubeFace(3, 1, 5); // -Z + } else { + createInfo.baseWidth = totalWidth; + createInfo.baseHeight = totalHeight; + createInfo.numFaces = 1; + createInfo.numDimensions = 1 + (totalHeight > 1); - // auto loadCubeFace = [&kTexture, &faceWidth, &totalWidth, &data](int xPos, int yPos, int faceName) - // { - // std::vector vec(faceWidth * faceWidth * 4); - // for (uint32 y = 0; y < faceWidth; ++y) - // { - // for (uint32 x = 0; x < faceWidth; ++x) - // { - // int imgX = x + (xPos * faceWidth); - // int imgY = y + (yPos * faceWidth); - // std::memcpy(&vec[(x + (faceWidth * y)) * 4], &data[(imgX + (totalWidth * imgY)) * 4], 4); - // } - // } - // ktxTexture_SetImageFromMemory(ktxTexture(kTexture), - // 0, 0, faceName, vec.data(), vec.size()); - // }; - // loadCubeFace(2, 1, 0); // +X - // loadCubeFace(0, 1, 1); // -X - // loadCubeFace(1, 0, 2); // +Y - // loadCubeFace(1, 2, 3); // -Y - // loadCubeFace(1, 1, 4); // +Z - // loadCubeFace(3, 1, 5); // -Z - //} - // else - //{ - // createInfo.baseWidth = totalWidth; - // createInfo.baseHeight = totalHeight; - // createInfo.numFaces = 1; - // createInfo.numDimensions = 1 + (totalHeight > 1); + ktxTexture2_Create(&createInfo, KTX_TEXTURE_CREATE_ALLOC_STORAGE, &kTexture); - // ktxTexture2_Create(&createInfo, - // KTX_TEXTURE_CREATE_ALLOC_STORAGE, - // &kTexture); + ktxTexture_SetImageFromMemory(ktxTexture(kTexture), 0, 0, 0, data, totalWidth * totalHeight * n * sizeof(unsigned char)); + } + ktxBasisParams basisParams = { + .structSize = sizeof(ktxBasisParams), + .uastc = true, + .threadCount = std::thread::hardware_concurrency() - 2, + .uastcFlags = KTX_PACK_UASTC_LEVEL_SLOWER, + .uastcRDO = true, + }; + KTX_ASSERT(ktxTexture2_CompressBasisEx(kTexture, &basisParams)); - // ktxTexture_SetImageFromMemory(ktxTexture(kTexture), - // 0, 0, 0, data, totalWidth * totalHeight * n * sizeof(unsigned char)); - //} - // ktxTexture_WriteToNamedFile(ktxTexture(kTexture), args.filePath.replace_extension(".ktx").string().c_str()); + KTX_ASSERT(ktxTexture2_DeflateZstd(kTexture, 20)); - // ktxBasisParams basisParams = { - // .structSize = sizeof(ktxBasisParams), - // .uastc = true, - // .threadCount = std::thread::hardware_concurrency(), - // .normalMap = normalMap, - // .uastcFlags = KTX_PACK_UASTC_LEVEL_VERYSLOW, - // .uastcRDO = true, - // .uastcRDOQualityScalar = 1, - // }; - // KTX_ASSERT(ktxTexture2_CompressBasisEx(kTexture, &basisParams)); - // KTX_ASSERT(ktxTexture2_DeflateZstd(kTexture, 3)); + char writer[100]; + snprintf(writer, sizeof(writer), "%s version %s", "SeeleEngine", "0.0.1"); + ktxHashList_AddKVPair(&kTexture->kvDataHead, KTX_WRITER_KEY, (ktx_uint32_t)strlen(writer) + 1, writer); - // char writer[100]; - // snprintf(writer, sizeof(writer), "%s version %s", "SeeleEngine", "0.0.1"); - // ktxHashList_AddKVPair(&kTexture->kvDataHead, KTX_WRITER_KEY, - // (ktx_uint32_t)strlen(writer) + 1, - // writer); + uint8* texData; + size_t texSize; + KTX_ASSERT(ktxTexture_WriteToMemory(ktxTexture(kTexture), &texData, &texSize)); - // uint8* texData; - // size_t texSize; - // KTX_ASSERT(ktxTexture_WriteToMemory(ktxTexture(kTexture), &texData, &texSize)); - // - // stbi_image_free(data); + ktxTexture_WriteToNamedFile(ktxTexture(kTexture), args.filePath.filename().replace_extension(".ktx").generic_string().c_str()); + + Array serialized(texSize); + std::memcpy(serialized.data(), texData, texSize); + + stbi_image_free(data); + ktxTexture_Destroy(ktxTexture(kTexture)); if (textureAsset->getName().empty()) { return; } + textureAsset->setTexture(std::move(serialized)); + std::string path = (std::filesystem::path(args.importPath) / textureAsset->getName()).string().append(".asset"); auto assetStream = AssetRegistry::createWriteStream(std::move(path), std::ios::binary); ArchiveBuffer buffer(graphics); @@ -179,7 +164,7 @@ void TextureLoader::import(TextureImportArgs args, PTextureAsset textureAsset) { // write folder Serialization::save(buffer, textureAsset->getFolderPath()); // write asset data - Serialization::save(buffer, args.filePath.string()); + textureAsset->save(buffer); buffer.writeToStream(assetStream); diff --git a/src/Editor/Window/PlayView.cpp b/src/Editor/Window/PlayView.cpp index 12bff3d..600451d 100644 --- a/src/Editor/Window/PlayView.cpp +++ b/src/Editor/Window/PlayView.cpp @@ -18,3 +18,31 @@ void PlayView::commitUpdate() { GameView::commitUpdate(); } void PlayView::prepareRender() { GameView::prepareRender(); } void PlayView::render() { GameView::render(); } + +void PlayView::keyCallback(KeyCode code, InputAction action, KeyModifier modifier) { + GameView::keyCallback(code, action, modifier); + if (code == KeyCode::KEY_P && action == InputAction::RELEASE) { + getGlobals().usePositionOnly = !getGlobals().usePositionOnly; + std::cout << "Use Pos only " << getGlobals().usePositionOnly << std::endl; + } + if (code == KeyCode::KEY_O && action == InputAction::RELEASE) { + getGlobals().useDepthCulling = !getGlobals().useDepthCulling; + std::cout << "Use Depth Culling " << getGlobals().useDepthCulling << std::endl; + } + if (code == KeyCode::KEY_L && action == InputAction::RELEASE) { + getGlobals().useLightCulling = !getGlobals().useLightCulling; + std::cout << "Use Light Culling " << getGlobals().useLightCulling << std::endl; + } + if (code == KeyCode::KEY_G && action == InputAction::RELEASE) { + Component::Camera cam; + Component::Transform tra; + scene->view([&cam, &tra](Component::Camera& c, Component::Transform& t) { + if (c.mainCamera) { + tra = t; + cam = c; + } + }); + std::cout << cam.getCameraPosition() << std::endl; + std::cout << tra.getRotation() << std::endl; + } +} diff --git a/src/Editor/Window/PlayView.h b/src/Editor/Window/PlayView.h index 82ed110..f236117 100644 --- a/src/Editor/Window/PlayView.h +++ b/src/Editor/Window/PlayView.h @@ -13,6 +13,8 @@ class PlayView : public GameView { virtual void prepareRender() override; virtual void render() override; + + virtual void keyCallback(Seele::KeyCode code, Seele::InputAction action, Seele::KeyModifier modifier) override; private: }; diff --git a/src/Editor/main.cpp b/src/Editor/main.cpp index 00eb234..8c58bdb 100644 --- a/src/Editor/main.cpp +++ b/src/Editor/main.cpp @@ -39,70 +39,72 @@ int main() { std::filesystem::path binaryPath = sourcePath / "cmake" / "libMeshShadingDemo.so"; #endif std::filesystem::path cmakePath = outputPath / "cmake"; - + if (true) { #ifdef __APPLE__ - graphics = new Metal::Graphics(); + graphics = new Metal::Graphics(); #else - graphics = new Vulkan::Graphics(); + graphics = new Vulkan::Graphics(); #endif - GraphicsInitializer initializer; - graphics->init(initializer); - StaticMeshVertexData* vd = StaticMeshVertexData::getInstance(); - vd->init(graphics); + GraphicsInitializer initializer; + graphics->init(initializer); + StaticMeshVertexData* vd = StaticMeshVertexData::getInstance(); + vd->init(graphics); - OWindowManager windowManager = new WindowManager(); - AssetRegistry::init(sourcePath / "Assets", graphics); - AssetImporter::init(graphics); - AssetImporter::importFont(FontImportArgs{ - .filePath = "./fonts/Calibri.ttf", - }); - AssetImporter::importMesh(MeshImportArgs{ - .filePath = sourcePath / "import/models/cube.fbx", - }); - AssetImporter::importTexture(TextureImportArgs{ - .filePath = sourcePath / "import/textures/skyboxsun5deg_tn.jpg", - .type = TextureImportType::TEXTURE_CUBEMAP, - }); - AssetImporter::importMesh(MeshImportArgs{ - .filePath = sourcePath / "import/models/after-the-rain-vr-sound/source/Whitechapel.fbx", - .importPath = "Whitechapel", - }); - AssetImporter::importMesh(MeshImportArgs{ - .filePath = sourcePath / "import/models/city-suburbs/source/city-suburbs.obj", - .importPath = "suburbs", - }); - vd->commitMeshes(); - WindowCreateInfo mainWindowInfo = { - .width = 1920, - .height = 1080, - .title = "SeeleEngine", - .preferredFormat = Gfx::SE_FORMAT_B8G8R8A8_SRGB, - }; - auto window = windowManager->addWindow(graphics, mainWindowInfo); - ViewportCreateInfo sceneViewInfo = { - .dimensions = - { - .size = {1920, 1080}, - .offset = {0, 0}, - }, - .numSamples = Gfx::SE_SAMPLE_COUNT_1_BIT, - }; - OGameView sceneView = new Editor::PlayView(graphics, window, sceneViewInfo, binaryPath.generic_string()); - sceneView->setFocused(); + OWindowManager windowManager = new WindowManager(); + AssetRegistry::init(sourcePath / "Assets", graphics); + AssetImporter::init(graphics); + // AssetImporter::importFont(FontImportArgs{ + // .filePath = "./fonts/Calibri.ttf", + // }); + AssetImporter::importMesh(MeshImportArgs{ + .filePath = sourcePath / "import/models/cube.fbx", + }); + AssetImporter::importTexture(TextureImportArgs{ + .filePath = sourcePath / "import/textures/skyboxsun5deg_tn.jpg", + .type = TextureImportType::TEXTURE_CUBEMAP, + }); + AssetImporter::importMesh(MeshImportArgs{ + .filePath = sourcePath / "import/models/after-the-rain-vr-sound/source/Whitechapel.fbx", + .importPath = "Whitechapel", + }); + // AssetImporter::importMesh(MeshImportArgs{ + // .filePath = sourcePath / "import/models/city-suburbs/source/city-suburbs.obj", + // .importPath = "suburbs", + // }); + vd->commitMeshes(); + WindowCreateInfo mainWindowInfo = { + .width = 1920, + .height = 1080, + .title = "SeeleEngine", + .preferredFormat = Gfx::SE_FORMAT_B8G8R8A8_SRGB, + }; + auto window = windowManager->addWindow(graphics, mainWindowInfo); + ViewportCreateInfo sceneViewInfo = { + .dimensions = + { + .size = {1920, 1080}, + .offset = {0, 0}, + }, + .numSamples = Gfx::SE_SAMPLE_COUNT_4_BIT, + }; + OGameView sceneView = new Editor::PlayView(graphics, window, sceneViewInfo, binaryPath.generic_string()); + sceneView->setFocused(); - while (windowManager->isActive()) { - windowManager->render(); - } - vd->destroy(); - // export game - if (false) { + while (windowManager->isActive() && getGlobals().running) { + windowManager->render(); + } + vd->destroy(); + // export game + + } else { std::filesystem::create_directories(outputPath); - std::system(fmt::format("cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DGAME_TITLE=\"{}\" -DGAME_DESTINATION=\"{}\" " + std::system(fmt::format("cmake -DCMAKE_BUILD_TYPE=Release -DGAME_TITLE=\"{}\" -DGAME_DESTINATION=\"{}\" " "-DGAME_BINARY=\"{}\" -P ./cmake/ExportProject.cmake", gameName, outputPath.generic_string(), binaryPath.generic_string()) .c_str()); - std::system(fmt::format("cmake -S {} -B {}", cmakePath.generic_string(), cmakePath.generic_string()).c_str()); + std::system(fmt::format("cmake -S {} -B {}", cmakePath.generic_string(), outputPath.generic_string()).c_str()); std::system(fmt::format("cmake --build {}", cmakePath.generic_string()).c_str()); + std::filesystem::copy(binaryPath, outputPath, std::filesystem::copy_options::recursive); std::filesystem::copy(sourcePath / "Assets", outputPath / "Assets", std::filesystem::copy_options::recursive); std::filesystem::copy("shaders", outputPath / "shaders", std::filesystem::copy_options::recursive); std::filesystem::copy("textures", outputPath / "textures", std::filesystem::copy_options::recursive); @@ -110,7 +112,6 @@ int main() { std::filesystem::copy_file("assimp-vc143-mt.dll", outputPath / "assimp-vc143-mt.dll"); std::filesystem::copy_file("slang.dll", outputPath / "slang.dll"); std::filesystem::copy_file("slang-glslang.dll", outputPath / "slang-glslang.dll"); - std::filesystem::copy_file("slang-llvm.dll", outputPath / "slang-llvm.dll"); #endif } return 0; diff --git a/src/Engine/Asset/TextureAsset.cpp b/src/Engine/Asset/TextureAsset.cpp index 593b8dc..58b147d 100644 --- a/src/Engine/Asset/TextureAsset.cpp +++ b/src/Engine/Asset/TextureAsset.cpp @@ -5,7 +5,6 @@ #include "Window/WindowManager.h" #include "ktx.h" - using namespace Seele; #define KTX_ASSERT(x) \ @@ -24,35 +23,17 @@ TextureAsset::TextureAsset(std::string_view folderPath, std::string_view name) : TextureAsset::~TextureAsset() {} void TextureAsset::save(ArchiveBuffer& buffer) const { - // ktxBasisParams basisParams = { - // .structSize = sizeof(ktxBasisParams), - // .uastc = true, - // .threadCount = std::thread::hardware_concurrency(), - // .normalMap = normalMap, - // .uastcFlags = KTX_PACK_UASTC_LEVEL_VERYSLOW, - // .uastcRDO = true, - // .uastcRDOQualityScalar = 1, - // }; - // KTX_ASSERT(ktxTexture2_CompressBasisEx(ktxHandle, &basisParams)); - // KTX_ASSERT(ktxTexture2_DeflateZstd(ktxHandle, 20)); - // ktx_uint8_t* texData; - // ktx_size_t texSize; - // KTX_ASSERT(ktxTexture_WriteToMemory(ktxTexture(ktxHandle), &texData, &texSize)); - // - // Array rawData(texSize); - // std::memcpy(rawData.data(), texData, texSize); - // Serialization::save(buffer, rawData); - // free(texData); + Serialization::save(buffer, ktxData); } void TextureAsset::load(ArchiveBuffer& buffer) { - std::string ktxPath; - Serialization::load(buffer, ktxPath); ktxTexture2* ktxHandle; + Serialization::load(buffer, ktxData); + KTX_ASSERT( + ktxTexture_CreateFromMemory(ktxData.data(), ktxData.size(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, (ktxTexture**)&ktxHandle)); - KTX_ASSERT(ktxTexture_CreateFromNamedFile(ktxPath.c_str(), KTX_TEXTURE_CREATE_NO_FLAGS, (ktxTexture**)&ktxHandle)); - - KTX_ASSERT(ktxTexture2_TranscodeBasis(ktxHandle, KTX_TTF_BC7_RGBA, 0)); + ktxTexture2_TranscodeBasis(ktxHandle, KTX_TTF_RGBA32, 0); + //ktxTexture2_DeflateZstd(ktxHandle, 0); Gfx::PGraphics graphics = buffer.getGraphics(); TextureCreateInfo createInfo = { @@ -60,7 +41,7 @@ void TextureAsset::load(ArchiveBuffer& buffer) { { .size = ktxTexture_GetDataSize(ktxTexture(ktxHandle)), .data = ktxTexture_GetData(ktxTexture(ktxHandle)), - .owner = Gfx::QueueType::TRANSFER, + .owner = Gfx::QueueType::GRAPHICS, }, .format = (Gfx::SeFormat)ktxHandle->vkFormat, .width = ktxHandle->baseWidth, @@ -79,12 +60,9 @@ void TextureAsset::load(ArchiveBuffer& buffer) { texture = graphics->createTexture2D(createInfo); } - texture->transferOwnership(Gfx::QueueType::GRAPHICS); ktxTexture_Destroy(ktxTexture(ktxHandle)); } -void TextureAsset::setTexture(Gfx::OTexture _texture) { texture = std::move(_texture); } - uint32 TextureAsset::getWidth() { return texture->getWidth(); } uint32 TextureAsset::getHeight() { return texture->getHeight(); } diff --git a/src/Engine/Asset/TextureAsset.h b/src/Engine/Asset/TextureAsset.h index 561ccc6..b42ccd3 100644 --- a/src/Engine/Asset/TextureAsset.h +++ b/src/Engine/Asset/TextureAsset.h @@ -12,14 +12,14 @@ class TextureAsset : public Asset { virtual ~TextureAsset(); virtual void save(ArchiveBuffer& buffer) const override; virtual void load(ArchiveBuffer& buffer) override; - void setTexture(Gfx::OTexture _texture); Gfx::PTexture getTexture() { return texture; } + void setTexture(Array data) { ktxData = std::move(data); } uint32 getWidth(); uint32 getHeight(); private: Gfx::OTexture texture; - bool normalMap; + Array ktxData; friend class TextureLoader; }; DEFINE_REF(TextureAsset) diff --git a/src/Engine/Graphics/Query.h b/src/Engine/Graphics/Query.h index 0bf7a97..0d394a0 100644 --- a/src/Engine/Graphics/Query.h +++ b/src/Engine/Graphics/Query.h @@ -51,7 +51,7 @@ class PipelineStatisticsQuery { DEFINE_REF(PipelineStatisticsQuery) struct Timestamp { std::string name; - std::chrono::nanoseconds time; + uint64 time; }; class TimestampQuery { public: diff --git a/src/Engine/Graphics/RenderPass/BasePass.cpp b/src/Engine/Graphics/RenderPass/BasePass.cpp index 5d19696..a869028 100644 --- a/src/Engine/Graphics/RenderPass/BasePass.cpp +++ b/src/Engine/Graphics/RenderPass/BasePass.cpp @@ -1,5 +1,6 @@ #include "BasePass.h" #include "Actor/CameraActor.h" +#include "Asset/AssetRegistry.h" #include "Component/Camera.h" #include "Component/Mesh.h" #include "Graphics/Command.h" @@ -7,6 +8,7 @@ #include "Graphics/Enums.h" #include "Graphics/Graphics.h" #include "Graphics/Initializer.h" +#include "Graphics/Pipeline.h" #include "Graphics/Shader.h" #include "Graphics/StaticMeshVertexData.h" #include "Material/MaterialInstance.h" @@ -16,6 +18,12 @@ using namespace Seele; +Array gDebugVertices; + +void Seele::addDebugVertex(DebugVertex vert) { gDebugVertices.add(vert); } + +void Seele::addDebugVertices(Array verts) { gDebugVertices.addAll(verts); } + BasePass::BasePass(Gfx::PGraphics graphics, PScene scene) : RenderPass(graphics, scene) { basePassLayout = graphics->createPipelineLayout("BasePassLayout"); @@ -68,6 +76,12 @@ BasePass::BasePass(Gfx::PGraphics graphics, PScene scene) : RenderPass(graphics, .useVisibility = false, }); } + skybox = Seele::Component::Skybox{ + .day = AssetRegistry::findTexture("", "skyboxsun5deg_tn")->getTexture().cast(), + .night = AssetRegistry::findTexture("", "skyboxsun5deg_tn")->getTexture().cast(), + .fogColor = Vector(0.1, 0.1, 0.8), + .blendFactor = 0, + }; } BasePass::~BasePass() {} @@ -81,6 +95,41 @@ void BasePass::beginFrame(const Component::Camera& cam) { lightCullingLayout->reset(); opaqueCulling = lightCullingLayout->allocateDescriptorSet(); transparentCulling = lightCullingLayout->allocateDescriptorSet(); + + // Debug vertices + VertexBufferCreateInfo vertexBufferInfo = { + .sourceData = + { + .size = sizeof(DebugVertex) * gDebugVertices.size(), + .data = (uint8*)gDebugVertices.data(), + }, + .vertexSize = sizeof(DebugVertex), + .numVertices = (uint32)gDebugVertices.size(), + .name = "DebugVertices", + }; + debugVertices = graphics->createVertexBuffer(vertexBufferInfo); + debugVertices->pipelineBarrier(Gfx::SE_ACCESS_TRANSFER_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_TRANSFER_BIT, + Gfx::SE_ACCESS_VERTEX_ATTRIBUTE_READ_BIT, Gfx::SE_PIPELINE_STAGE_VERTEX_INPUT_BIT); + + // Skybox + skyboxDataLayout->reset(); + textureLayout->reset(); + skyboxData.transformMatrix = glm::rotate(skyboxData.transformMatrix, (float)(Gfx::getCurrentFrameDelta()), Vector(0, 1, 0)); + skyboxBuffer->rotateBuffer(sizeof(SkyboxData)); + skyboxBuffer->updateContents(DataSource{ + .size = sizeof(SkyboxData), + .data = (uint8*)&skyboxData, + }); + skyboxDataSet = skyboxDataLayout->allocateDescriptorSet(); + skyboxDataSet->updateBuffer(0, skyboxBuffer); + skyboxDataSet->writeChanges(); + skyboxBuffer->pipelineBarrier(Gfx::SE_ACCESS_TRANSFER_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_TRANSFER_BIT, Gfx::SE_ACCESS_UNIFORM_READ_BIT, + Gfx::SE_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); + textureSet = textureLayout->allocateDescriptorSet(); + textureSet->updateTexture(0, skybox.day); + textureSet->updateTexture(1, skybox.night); + textureSet->updateSampler(2, skyboxSampler); + textureSet->writeChanges(); } void BasePass::render() { @@ -95,7 +144,6 @@ void BasePass::render() { timestamps->write(Gfx::SE_PIPELINE_STAGE_TOP_OF_PIPE_BIT, "BASEPASS"); graphics->beginRenderPass(renderPass); Array commands; - Gfx::ShaderPermutation permutation = graphics->getShaderCompiler()->getTemplate("BasePass"); permutation.setDepthCulling(true); // always use the culling info permutation.setPositionOnly(false); @@ -193,110 +241,137 @@ void BasePass::render() { commands.add(std::move(command)); } } + graphics->executeCommands(std::move(commands)); - Map sortedDraws; - for (const auto& t : transparentData) { - Vector toCenter = Vector(t.worldPosition) - cameraPos; - float dist = glm::length(toCenter) * glm::dot(glm::normalize(toCenter), cameraForward); - sortedDraws[dist] = t; + commands.clear(); + // Transparent rendering + { + Map sortedDraws; + for (const auto& t : transparentData) { + Vector toCenter = Vector(t.worldPosition) - cameraPos; + float dist = glm::length(toCenter) * glm::dot(glm::normalize(toCenter), cameraForward); + sortedDraws[dist] = t; + } + Gfx::ORenderCommand transparentCommand = graphics->createRenderCommand("TransparentDraw"); + transparentCommand->setViewport(viewport); + for (const auto& [_, t] : sortedDraws) { + permutation.setVertexData(t.vertexData->getTypeName()); + permutation.setMaterial(t.matInst->getBaseMaterial()->getName()); + Gfx::PermutationId id(permutation); + + const Gfx::ShaderCollection* collection = graphics->getShaderCompiler()->findShaders(id); + assert(collection != nullptr); + + bool twoSided = t.matInst->getBaseMaterial()->isTwoSided(); + + if (graphics->supportMeshShading()) { + Gfx::MeshPipelineCreateInfo pipelineInfo = { + .taskShader = collection->taskShader, + .meshShader = collection->meshShader, + .fragmentShader = collection->fragmentShader, + .renderPass = renderPass, + .pipelineLayout = collection->pipelineLayout, + .multisampleState = + { + .samples = viewport->getSamples(), + }, + .rasterizationState = + { + .cullMode = Gfx::SeCullModeFlags(twoSided ? Gfx::SE_CULL_MODE_NONE : Gfx::SE_CULL_MODE_BACK_BIT), + }, + .depthStencilState = + { + .depthCompareOp = Gfx::SE_COMPARE_OP_GREATER_OR_EQUAL, + }, + .colorBlend = + { + .attachmentCount = 1, + .blendAttachments = + { + Gfx::ColorBlendState::BlendAttachment{ + .blendEnable = true, + }, + }, + }, + }; + Gfx::PGraphicsPipeline pipeline = graphics->createGraphicsPipeline(std::move(pipelineInfo)); + transparentCommand->bindPipeline(pipeline); + } else { + Gfx::LegacyPipelineCreateInfo pipelineInfo = { + .vertexShader = collection->vertexShader, + .fragmentShader = collection->fragmentShader, + .renderPass = renderPass, + .pipelineLayout = collection->pipelineLayout, + .multisampleState = + { + .samples = viewport->getSamples(), + }, + .rasterizationState = + { + .cullMode = Gfx::SeCullModeFlags(twoSided ? Gfx::SE_CULL_MODE_NONE : Gfx::SE_CULL_MODE_BACK_BIT), + }, + .depthStencilState = + { + .depthCompareOp = Gfx::SE_COMPARE_OP_GREATER_OR_EQUAL, + }, + .colorBlend = + { + .attachmentCount = 1, + .blendAttachments = + { + Gfx::ColorBlendState::BlendAttachment{ + .blendEnable = true, + }, + }, + }, + }; + Gfx::PGraphicsPipeline pipeline = graphics->createGraphicsPipeline(std::move(pipelineInfo)); + transparentCommand->bindPipeline(pipeline); + } + transparentCommand->bindDescriptor({viewParamsSet, t.vertexData->getVertexDataSet(), t.vertexData->getInstanceDataSet(), + scene->getLightEnvironment()->getDescriptorSet(), Material::getDescriptorSet(), + transparentCulling}); + transparentCommand->pushConstants(Gfx::SE_SHADER_STAGE_TASK_BIT_EXT | Gfx::SE_SHADER_STAGE_VERTEX_BIT | + Gfx::SE_SHADER_STAGE_FRAGMENT_BIT, + 0, sizeof(VertexData::DrawCallOffsets), &t.offsets); + if (graphics->supportMeshShading()) { + transparentCommand->drawMesh(1, 1, 1); + } else { + // command->bindIndexBuffer(t.vertexData->getIndexBuffer()); + // for (const auto& meshData : drawCall.instanceMeshData) { + // // all meshlets of a mesh share the same indices offset + // command->drawIndexed(meshData.numIndices, 1, meshData.firstIndex, + // vertexData->getIndicesOffset(meshData.meshletOffset), 0); + // } + } + } + commands.add(std::move(transparentCommand)); } - Gfx::ORenderCommand command = graphics->createRenderCommand("TransparentDraw"); - command->setViewport(viewport); - for (const auto& [_, t] : sortedDraws) { - permutation.setVertexData(t.vertexData->getTypeName()); - permutation.setMaterial(t.matInst->getBaseMaterial()->getName()); - Gfx::PermutationId id(permutation); - - const Gfx::ShaderCollection* collection = graphics->getShaderCompiler()->findShaders(id); - assert(collection != nullptr); - - bool twoSided = t.matInst->getBaseMaterial()->isTwoSided(); - - if (graphics->supportMeshShading()) { - Gfx::MeshPipelineCreateInfo pipelineInfo = { - .taskShader = collection->taskShader, - .meshShader = collection->meshShader, - .fragmentShader = collection->fragmentShader, - .renderPass = renderPass, - .pipelineLayout = collection->pipelineLayout, - .multisampleState = - { - .samples = viewport->getSamples(), - }, - .rasterizationState = - { - .cullMode = Gfx::SeCullModeFlags(twoSided ? Gfx::SE_CULL_MODE_NONE : Gfx::SE_CULL_MODE_BACK_BIT), - }, - .depthStencilState = - { - .depthCompareOp = Gfx::SE_COMPARE_OP_GREATER_OR_EQUAL, - }, - .colorBlend = - { - .attachmentCount = 1, - .blendAttachments = - { - Gfx::ColorBlendState::BlendAttachment{ - .blendEnable = true, - }, - }, - }, - }; - Gfx::PGraphicsPipeline pipeline = graphics->createGraphicsPipeline(std::move(pipelineInfo)); - command->bindPipeline(pipeline); - } else { - Gfx::LegacyPipelineCreateInfo pipelineInfo = { - .vertexShader = collection->vertexShader, - .fragmentShader = collection->fragmentShader, - .renderPass = renderPass, - .pipelineLayout = collection->pipelineLayout, - .multisampleState = - { - .samples = viewport->getSamples(), - }, - .rasterizationState = - { - .cullMode = Gfx::SeCullModeFlags(twoSided ? Gfx::SE_CULL_MODE_NONE : Gfx::SE_CULL_MODE_BACK_BIT), - }, - .depthStencilState = - { - .depthCompareOp = Gfx::SE_COMPARE_OP_GREATER_OR_EQUAL, - }, - .colorBlend = - { - .attachmentCount = 1, - .blendAttachments = - { - Gfx::ColorBlendState::BlendAttachment{ - .blendEnable = true, - }, - }, - }, - }; - Gfx::PGraphicsPipeline pipeline = graphics->createGraphicsPipeline(std::move(pipelineInfo)); - command->bindPipeline(pipeline); - } - command->bindDescriptor({viewParamsSet, t.vertexData->getVertexDataSet(), t.vertexData->getInstanceDataSet(), - scene->getLightEnvironment()->getDescriptorSet(), Material::getDescriptorSet(), transparentCulling}); - command->pushConstants(Gfx::SE_SHADER_STAGE_TASK_BIT_EXT | Gfx::SE_SHADER_STAGE_VERTEX_BIT | Gfx::SE_SHADER_STAGE_FRAGMENT_BIT, 0, - sizeof(VertexData::DrawCallOffsets), &t.offsets); - if (graphics->supportMeshShading()) { - command->drawMesh(1, 1, 1); - } else { - // command->bindIndexBuffer(t.vertexData->getIndexBuffer()); - // for (const auto& meshData : drawCall.instanceMeshData) { - // // all meshlets of a mesh share the same indices offset - // command->drawIndexed(meshData.numIndices, 1, meshData.firstIndex, vertexData->getIndicesOffset(meshData.meshletOffset), - // 0); - // } - } + // Debug vertices + if (gDebugVertices.size() > 0) { + Gfx::ORenderCommand debugCommand = graphics->createRenderCommand("DebugRender"); + debugCommand->setViewport(viewport); + debugCommand->bindPipeline(debugPipeline); + debugCommand->bindDescriptor(viewParamsSet); + debugCommand->bindVertexBuffer({debugVertices}); + debugCommand->draw((uint32)gDebugVertices.size(), 1, 0, 0); + commands.add(std::move(debugCommand)); } + Gfx::ORenderCommand skyboxCommand = graphics->createRenderCommand("SkyboxRender"); + skyboxCommand->setViewport(viewport); + skyboxCommand->bindPipeline(pipeline); + skyboxCommand->bindDescriptor({viewParamsSet, skyboxDataSet, textureSet}); + skyboxCommand->draw(36, 1, 0, 0); + + commands.add(std::move(skyboxCommand)); + graphics->executeCommands(std::move(commands)); graphics->endRenderPass(); query->endQuery(); timestamps->write(Gfx::SE_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, "END"); timestamps->end(); + gDebugVertices.clear(); } void BasePass::endFrame() {} @@ -306,17 +381,44 @@ void BasePass::publishOutputs() { .format = Gfx::SE_FORMAT_D32_SFLOAT, .width = viewport->getOwner()->getFramebufferWidth(), .height = viewport->getOwner()->getFramebufferHeight(), - .samples = viewport->getSamples(), .usage = Gfx::SE_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, }; basePassDepth = graphics->createTexture2D(depthBufferInfo); - colorAttachment = Gfx::RenderTargetAttachment(viewport, Gfx::SE_IMAGE_LAYOUT_UNDEFINED, Gfx::SE_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - Gfx::SE_ATTACHMENT_LOAD_OP_CLEAR, Gfx::SE_ATTACHMENT_STORE_OP_STORE); - resources->registerRenderPassOutput("BASEPASS_COLOR", colorAttachment); + TextureCreateInfo msDepthInfo = { + .format = Gfx::SE_FORMAT_D32_SFLOAT, + .width = viewport->getOwner()->getFramebufferWidth(), + .height = viewport->getOwner()->getFramebufferHeight(), + .samples = viewport->getSamples(), + .usage = Gfx::SE_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | Gfx::SE_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT, + }; + msBasePassDepth = graphics->createTexture2D(msDepthInfo); + + TextureCreateInfo msBaseColorInfo = { + .format = viewport->getOwner()->getSwapchainFormat(), + .width = viewport->getOwner()->getFramebufferWidth(), + .height = viewport->getOwner()->getFramebufferHeight(), + .samples = viewport->getSamples(), + .usage = Gfx::SE_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | Gfx::SE_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT, + }; + msBasePassColor = graphics->createTexture2D(msBaseColorInfo); + depthAttachment = Gfx::RenderTargetAttachment(basePassDepth, Gfx::SE_IMAGE_LAYOUT_UNDEFINED, Gfx::SE_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, - Gfx::SE_ATTACHMENT_LOAD_OP_CLEAR, Gfx::SE_ATTACHMENT_STORE_OP_STORE); + Gfx::SE_ATTACHMENT_LOAD_OP_DONT_CARE, Gfx::SE_ATTACHMENT_STORE_OP_STORE); + + msDepthAttachment = + Gfx::RenderTargetAttachment(msBasePassDepth, Gfx::SE_IMAGE_LAYOUT_UNDEFINED, Gfx::SE_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + Gfx::SE_ATTACHMENT_LOAD_OP_CLEAR, Gfx::SE_ATTACHMENT_STORE_OP_DONT_CARE); + + colorAttachment = Gfx::RenderTargetAttachment(viewport, Gfx::SE_IMAGE_LAYOUT_UNDEFINED, Gfx::SE_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + Gfx::SE_ATTACHMENT_LOAD_OP_DONT_CARE, Gfx::SE_ATTACHMENT_STORE_OP_STORE); + + msColorAttachment = + Gfx::RenderTargetAttachment(msBasePassColor, Gfx::SE_IMAGE_LAYOUT_UNDEFINED, Gfx::SE_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + Gfx::SE_ATTACHMENT_LOAD_OP_CLEAR, Gfx::SE_ATTACHMENT_STORE_OP_DONT_CARE); + + resources->registerRenderPassOutput("BASEPASS_COLOR", colorAttachment); resources->registerRenderPassOutput("BASEPASS_DEPTH", depthAttachment); query = graphics->createPipelineStatisticsQuery("BasePassPipelineStatistics"); @@ -328,15 +430,19 @@ void BasePass::createRenderPass() { cullingBuffer = resources->requestBuffer("CULLINGBUFFER"); Gfx::RenderTargetLayout layout = Gfx::RenderTargetLayout{ - .colorAttachments = {colorAttachment}, - .depthAttachment = depthAttachment, + .colorAttachments = {msColorAttachment}, + .resolveAttachments = {colorAttachment}, + .depthAttachment = msDepthAttachment, + .depthResolveAttachment = {depthAttachment}, }; Array dependency = { { .srcSubpass = ~0U, .dstSubpass = 0, - .srcStage = Gfx::SE_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | Gfx::SE_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, - .dstStage = Gfx::SE_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | Gfx::SE_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, + .srcStage = Gfx::SE_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | Gfx::SE_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | + Gfx::SE_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + .dstStage = Gfx::SE_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | Gfx::SE_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | + Gfx::SE_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, .srcAccess = Gfx::SE_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, .dstAccess = Gfx::SE_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | @@ -345,8 +451,10 @@ void BasePass::createRenderPass() { { .srcSubpass = 0, .dstSubpass = ~0U, - .srcStage = Gfx::SE_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | Gfx::SE_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, - .dstStage = Gfx::SE_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | Gfx::SE_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, + .srcStage = Gfx::SE_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | Gfx::SE_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | + Gfx::SE_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + .dstStage = Gfx::SE_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | Gfx::SE_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | + Gfx::SE_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, .srcAccess = Gfx::SE_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, .dstAccess = Gfx::SE_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | @@ -358,4 +466,151 @@ void BasePass::createRenderPass() { tLightIndexList = resources->requestBuffer("LIGHTCULLING_TLIGHTLIST"); oLightGrid = resources->requestTexture("LIGHTCULLING_OLIGHTGRID"); tLightGrid = resources->requestTexture("LIGHTCULLING_TLIGHTGRID"); + + // Debug rendering + { + debugPipelineLayout = graphics->createPipelineLayout("DebugPassLayout"); + debugPipelineLayout->addDescriptorLayout(viewParamsLayout); + + ShaderCreateInfo createInfo = { + .name = "DebugVertex", + .mainModule = "Debug", + .entryPoint = "vertexMain", + .rootSignature = debugPipelineLayout, + }; + debugVertexShader = graphics->createVertexShader(createInfo); + + createInfo.name = "DebugFragment"; + createInfo.entryPoint = "fragmentMain"; + debugFragmentShader = graphics->createFragmentShader(createInfo); + debugPipelineLayout->create(); + + VertexInputStateCreateInfo inputCreate = { + .bindings = + { + VertexInputBinding{ + .binding = 0, + .stride = sizeof(DebugVertex), + .inputRate = Gfx::SE_VERTEX_INPUT_RATE_VERTEX, + }, + }, + .attributes = + { + VertexInputAttribute{ + .location = 0, + .binding = 0, + .format = Gfx::SE_FORMAT_R32G32B32_SFLOAT, + .offset = 0, + }, + VertexInputAttribute{ + .location = 1, + .binding = 0, + .format = Gfx::SE_FORMAT_R32G32B32_SFLOAT, + .offset = sizeof(Vector), + }, + }, + }; + debugVertexInput = graphics->createVertexInput(inputCreate); + Gfx::LegacyPipelineCreateInfo gfxInfo = { + .topology = Gfx::SE_PRIMITIVE_TOPOLOGY_LINE_LIST, + .vertexInput = debugVertexInput, + .vertexShader = debugVertexShader, + .fragmentShader = debugFragmentShader, + .renderPass = renderPass, + .pipelineLayout = debugPipelineLayout, + .multisampleState = + { + .samples = viewport->getSamples(), + }, + .rasterizationState = + { + .polygonMode = Gfx::SE_POLYGON_MODE_LINE, + .lineWidth = 5.f, + }, + .colorBlend = + { + .attachmentCount = 1, + }, + }; + debugPipeline = graphics->createGraphicsPipeline(std::move(gfxInfo)); + } + + // Skybox + { + skyboxDataLayout = graphics->createDescriptorLayout("pSkyboxData"); + skyboxDataLayout->addDescriptorBinding(Gfx::DescriptorBinding{ + .binding = 0, + .descriptorType = Gfx::SE_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + }); + skyboxDataLayout->create(); + textureLayout = graphics->createDescriptorLayout("pSkyboxTextures"); + textureLayout->addDescriptorBinding(Gfx::DescriptorBinding{ + .binding = 0, + .descriptorType = Gfx::SE_DESCRIPTOR_TYPE_SAMPLED_IMAGE, + }); + textureLayout->addDescriptorBinding(Gfx::DescriptorBinding{ + .binding = 1, + .descriptorType = Gfx::SE_DESCRIPTOR_TYPE_SAMPLED_IMAGE, + }); + textureLayout->addDescriptorBinding(Gfx::DescriptorBinding{ + .binding = 2, + .descriptorType = Gfx::SE_DESCRIPTOR_TYPE_SAMPLER, + }); + textureLayout->create(); + + skyboxSampler = graphics->createSampler({}); + + skyboxData.transformMatrix = Matrix4(1); + skyboxData.fogColor = skybox.fogColor; + skyboxData.blendFactor = skybox.blendFactor; + + skyboxBuffer = graphics->createUniformBuffer(UniformBufferCreateInfo{ + .sourceData = + { + .size = sizeof(SkyboxData), + .data = (uint8*)&skyboxData, + }, + .dynamic = true, + }); + + pipelineLayout = graphics->createPipelineLayout("SkyboxLayout"); + pipelineLayout->addDescriptorLayout(viewParamsLayout); + pipelineLayout->addDescriptorLayout(skyboxDataLayout); + pipelineLayout->addDescriptorLayout(textureLayout); + + ShaderCreateInfo createInfo = { + .name = "SkyboxVertex", + .mainModule = "Skybox", + .entryPoint = "vertexMain", + .rootSignature = pipelineLayout, + }; + vertexShader = graphics->createVertexShader(createInfo); + + createInfo.name = "SkyboxFragment"; + createInfo.entryPoint = "fragmentMain"; + fragmentShader = graphics->createFragmentShader(createInfo); + + pipelineLayout->create(); + + Gfx::LegacyPipelineCreateInfo gfxInfo = { + .topology = Gfx::SE_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + .vertexShader = vertexShader, + .fragmentShader = fragmentShader, + .renderPass = renderPass, + .pipelineLayout = pipelineLayout, + .multisampleState = + { + .samples = viewport->getSamples(), + }, + .rasterizationState = + { + .polygonMode = Gfx::SE_POLYGON_MODE_FILL, + }, + .colorBlend = + { + .attachmentCount = 1, + }, + }; + pipeline = graphics->createGraphicsPipeline(std::move(gfxInfo)); + } } diff --git a/src/Engine/Graphics/RenderPass/BasePass.h b/src/Engine/Graphics/RenderPass/BasePass.h index 73c4e99..101b3f7 100644 --- a/src/Engine/Graphics/RenderPass/BasePass.h +++ b/src/Engine/Graphics/RenderPass/BasePass.h @@ -17,7 +17,9 @@ class BasePass : public RenderPass { virtual void createRenderPass() override; private: + Gfx::RenderTargetAttachment msColorAttachment; Gfx::RenderTargetAttachment colorAttachment; + Gfx::RenderTargetAttachment msDepthAttachment; Gfx::RenderTargetAttachment depthAttachment; Gfx::RenderTargetAttachment visibilityAttachment; Gfx::PShaderBuffer oLightIndexList; @@ -29,7 +31,9 @@ class BasePass : public RenderPass { Gfx::PDescriptorSet transparentCulling; // use a different texture here so we can do multisampling + Gfx::OTexture2D msBasePassDepth; Gfx::OTexture2D basePassDepth; + Gfx::OTexture2D msBasePassColor; // used for transparency sorting Vector cameraPos; @@ -43,6 +47,32 @@ class BasePass : public RenderPass { Gfx::PTimestampQuery timestamps; Gfx::PShaderBuffer cullingBuffer; + + // Debug rendering + Gfx::OVertexInput debugVertexInput; + Gfx::OVertexBuffer debugVertices; + Gfx::OVertexShader debugVertexShader; + Gfx::OFragmentShader debugFragmentShader; + Gfx::PGraphicsPipeline debugPipeline; + Gfx::OPipelineLayout debugPipelineLayout; + + // Skybox + Gfx::ODescriptorLayout skyboxDataLayout; + Gfx::PDescriptorSet skyboxDataSet; + Gfx::ODescriptorLayout textureLayout; + Gfx::PDescriptorSet textureSet; + Gfx::OVertexShader vertexShader; + Gfx::OFragmentShader fragmentShader; + Gfx::OPipelineLayout pipelineLayout; + Gfx::PGraphicsPipeline pipeline; + Gfx::OSampler skyboxSampler; + struct SkyboxData { + Matrix4 transformMatrix; + Vector fogColor; + float blendFactor; + } skyboxData; + Gfx::OUniformBuffer skyboxBuffer; + Component::Skybox skybox; }; DEFINE_REF(BasePass) } // namespace Seele diff --git a/src/Engine/Graphics/RenderPass/CMakeLists.txt b/src/Engine/Graphics/RenderPass/CMakeLists.txt index f5ff9d8..c7dc368 100644 --- a/src/Engine/Graphics/RenderPass/CMakeLists.txt +++ b/src/Engine/Graphics/RenderPass/CMakeLists.txt @@ -4,8 +4,6 @@ target_sources(Engine BasePass.cpp CachedDepthPass.h CachedDepthPass.cpp - DebugPass.h - DebugPass.cpp DepthCullingPass.h DepthCullingPass.cpp LightCullingPass.h @@ -17,8 +15,6 @@ target_sources(Engine RenderGraphResources.cpp RenderPass.h RenderPass.cpp - SkyboxRenderPass.h - SkyboxRenderPass.cpp TextPass.h TextPass.cpp UIPass.h @@ -31,14 +27,12 @@ target_sources(Engine FILES BasePass.h CachedDepthPass.h - DebugPass.h DepthCullingPass.h LightCullingPass.h RayTracingPass.h RenderGraph.h RenderGraphResources.h RenderPass.h - SkyboxRenderPass.h TextPass.h UIPass.h VisibilityPass.h) \ No newline at end of file diff --git a/src/Engine/Graphics/RenderPass/CachedDepthPass.cpp b/src/Engine/Graphics/RenderPass/CachedDepthPass.cpp index 057f40b..6fe28d4 100644 --- a/src/Engine/Graphics/RenderPass/CachedDepthPass.cpp +++ b/src/Engine/Graphics/RenderPass/CachedDepthPass.cpp @@ -3,9 +3,6 @@ using namespace Seele; -extern bool usePositionOnly; -extern bool useDepthCulling; - CachedDepthPass::CachedDepthPass(Gfx::PGraphics graphics, PScene scene) : RenderPass(graphics, scene) { depthPrepassLayout = graphics->createPipelineLayout("CachedDepthLayout"); depthPrepassLayout->addDescriptorLayout(viewParamsLayout); @@ -43,7 +40,9 @@ CachedDepthPass::CachedDepthPass(Gfx::PGraphics graphics, PScene scene) : Render CachedDepthPass::~CachedDepthPass() {} -void CachedDepthPass::beginFrame(const Component::Camera& cam) { RenderPass::beginFrame(cam); } +void CachedDepthPass::beginFrame(const Component::Camera& cam) { + RenderPass::beginFrame(cam); +} void CachedDepthPass::render() { query->beginQuery(); @@ -53,8 +52,8 @@ void CachedDepthPass::render() { Array commands; Gfx::ShaderPermutation permutation = graphics->getShaderCompiler()->getTemplate("CachedDepthPass"); - permutation.setPositionOnly(usePositionOnly); - permutation.setDepthCulling(useDepthCulling); + permutation.setPositionOnly(getGlobals().usePositionOnly); + permutation.setDepthCulling(getGlobals().useDepthCulling); for (VertexData* vertexData : VertexData::getList()) { permutation.setVertexData(vertexData->getTypeName()); vertexData->getInstanceDataSet()->updateBuffer(6, cullingBuffer); @@ -79,10 +78,6 @@ void CachedDepthPass::render() { .fragmentShader = collection->fragmentShader, .renderPass = renderPass, .pipelineLayout = collection->pipelineLayout, - .multisampleState = - { - .samples = viewport->getSamples(), - }, .colorBlend = { .attachmentCount = 1, @@ -96,10 +91,6 @@ void CachedDepthPass::render() { .fragmentShader = collection->fragmentShader, .renderPass = renderPass, .pipelineLayout = collection->pipelineLayout, - .multisampleState = - { - .samples = viewport->getSamples(), - }, .colorBlend = { .attachmentCount = 1, diff --git a/src/Engine/Graphics/RenderPass/DebugPass.cpp b/src/Engine/Graphics/RenderPass/DebugPass.cpp deleted file mode 100644 index a4cd74d..0000000 --- a/src/Engine/Graphics/RenderPass/DebugPass.cpp +++ /dev/null @@ -1,156 +0,0 @@ -#include "DebugPass.h" -#include "Graphics/Graphics.h" -#include "Graphics/Pipeline.h" -#include "Graphics/RenderTarget.h" -#include "Graphics/Shader.h" - - -using namespace Seele; - -Array gDebugVertices; - -void Seele::addDebugVertex(DebugVertex vert) { gDebugVertices.add(vert); } - -void Seele::addDebugVertices(Array verts) { gDebugVertices.addAll(verts); } - -DebugPass::DebugPass(Gfx::PGraphics graphics, PScene scene) : RenderPass(graphics, scene) {} - -DebugPass::~DebugPass() {} - -void DebugPass::beginFrame(const Component::Camera& cam) { - RenderPass::beginFrame(cam); - - VertexBufferCreateInfo vertexBufferInfo = { - .sourceData = - { - .size = sizeof(DebugVertex) * gDebugVertices.size(), - .data = (uint8*)gDebugVertices.data(), - }, - .vertexSize = sizeof(DebugVertex), - .numVertices = (uint32)gDebugVertices.size(), - .name = "DebugVertices", - }; - debugVertices = graphics->createVertexBuffer(vertexBufferInfo); -} - -void DebugPass::render() { - graphics->beginRenderPass(renderPass); - if (gDebugVertices.size() > 0) { - Gfx::ORenderCommand renderCommand = graphics->createRenderCommand("DebugRender"); - renderCommand->setViewport(viewport); - renderCommand->bindPipeline(pipeline); - renderCommand->bindDescriptor(viewParamsSet); - renderCommand->bindVertexBuffer({debugVertices}); - renderCommand->draw((uint32)gDebugVertices.size(), 1, 0, 0); - Array commands; - commands.add(std::move(renderCommand)); - graphics->executeCommands({std::move(commands)}); - } - graphics->endRenderPass(); - gDebugVertices.clear(); - // Sync color write with next pass/swapchain present - // colorAttachment.getTexture()->pipelineBarrier( - // Gfx::SE_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, - // Gfx::SE_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - // Gfx::SE_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, - // Gfx::SE_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT - //); - // Sync depth with next pass/next frame - // depthAttachment.getTexture()->pipelineBarrier( - // Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, - // Gfx::SE_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, - // Gfx::SE_ACCESS_MEMORY_WRITE_BIT, - // Gfx::SE_PIPELINE_STAGE_TOP_OF_PIPE_BIT - //); -} - -void DebugPass::endFrame() {} - -void DebugPass::publishOutputs() {} - -void DebugPass::createRenderPass() { - colorAttachment = resources->requestRenderTarget("BASEPASS_COLOR"); - colorAttachment.setLoadOp(Gfx::SE_ATTACHMENT_LOAD_OP_LOAD); - colorAttachment.setInitialLayout(Gfx::SE_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); - colorAttachment.setInitialLayout(Gfx::SE_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); - - depthAttachment = resources->requestRenderTarget("BASEPASS_DEPTH"); - depthAttachment.setLoadOp(Gfx::SE_ATTACHMENT_LOAD_OP_LOAD); - depthAttachment.setInitialLayout(Gfx::SE_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); - depthAttachment.setFinalLayout(Gfx::SE_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); - Gfx::RenderTargetLayout layout = Gfx::RenderTargetLayout{ - .colorAttachments = {colorAttachment}, - .depthAttachment = depthAttachment, - }; - - Array dependency = { - { - .srcSubpass = ~0U, - .dstSubpass = 0, - .srcStage = Gfx::SE_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | Gfx::SE_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, - .dstStage = Gfx::SE_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | Gfx::SE_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, - .srcAccess = Gfx::SE_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | - Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, - .dstAccess = Gfx::SE_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | - Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, - }, - { - .srcSubpass = 0, - .dstSubpass = ~0U, - .srcStage = Gfx::SE_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | Gfx::SE_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, - .dstStage = Gfx::SE_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | Gfx::SE_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, - .srcAccess = Gfx::SE_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | - Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, - .dstAccess = Gfx::SE_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | - Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, - }, - }; - renderPass = graphics->createRenderPass(std::move(layout), dependency, viewport); - - pipelineLayout = graphics->createPipelineLayout("DebugPassLayout"); - pipelineLayout->addDescriptorLayout(viewParamsLayout); - - ShaderCreateInfo createInfo = { - .name = "DebugVertex", - .mainModule = "Debug", - .entryPoint = "vertexMain", - .rootSignature = pipelineLayout, - }; - vertexShader = graphics->createVertexShader(createInfo); - - createInfo.name = "DebugFragment"; - createInfo.entryPoint = "fragmentMain"; - fragmentShader = graphics->createFragmentShader(createInfo); - pipelineLayout->create(); - - VertexInputStateCreateInfo inputCreate = { - .bindings = - { - VertexInputBinding{ - .binding = 0, - .stride = sizeof(DebugVertex), - .inputRate = Gfx::SE_VERTEX_INPUT_RATE_VERTEX, - }, - }, - .attributes = {VertexInputAttribute{ - .location = 0, - .binding = 0, - .format = Gfx::SE_FORMAT_R32G32B32_SFLOAT, - .offset = 0, - }, - VertexInputAttribute{ - .location = 1, .binding = 0, .format = Gfx::SE_FORMAT_R32G32B32_SFLOAT, .offset = sizeof(Vector)}}, - }; - vertexInput = graphics->createVertexInput(inputCreate); - Gfx::LegacyPipelineCreateInfo gfxInfo; - gfxInfo.vertexInput = vertexInput; - gfxInfo.vertexShader = vertexShader; - gfxInfo.fragmentShader = fragmentShader; - gfxInfo.rasterizationState.polygonMode = Gfx::SE_POLYGON_MODE_LINE; - gfxInfo.topology = Gfx::SE_PRIMITIVE_TOPOLOGY_LINE_LIST; - gfxInfo.pipelineLayout = std::move(pipelineLayout); - gfxInfo.renderPass = renderPass; - gfxInfo.colorBlend.attachmentCount = 1; - gfxInfo.rasterizationState.lineWidth = 5.f; - pipeline = graphics->createGraphicsPipeline(std::move(gfxInfo)); -} diff --git a/src/Engine/Graphics/RenderPass/DebugPass.h b/src/Engine/Graphics/RenderPass/DebugPass.h deleted file mode 100644 index b69253f..0000000 --- a/src/Engine/Graphics/RenderPass/DebugPass.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once -#include "Graphics/DebugVertex.h" -#include "Graphics/Pipeline.h" -#include "RenderPass.h" -#include "Scene/Scene.h" - - -namespace Seele { -DECLARE_REF(CameraActor) -DECLARE_REF(Scene) -DECLARE_REF(Viewport) -class DebugPass : public RenderPass { - public: - DebugPass(Gfx::PGraphics graphics, PScene scene); - DebugPass(DebugPass&&) = default; - DebugPass& operator=(DebugPass&&) = default; - virtual ~DebugPass(); - virtual void beginFrame(const Component::Camera& cam) override; - virtual void render() override; - virtual void endFrame() override; - virtual void publishOutputs() override; - virtual void createRenderPass() override; - - private: - Gfx::RenderTargetAttachment colorAttachment; - Gfx::RenderTargetAttachment depthAttachment; - Gfx::OVertexInput vertexInput; - Gfx::OVertexBuffer debugVertices; - Gfx::OVertexShader vertexShader; - Gfx::OFragmentShader fragmentShader; - Gfx::PGraphicsPipeline pipeline; - Gfx::OPipelineLayout pipelineLayout; -}; -DEFINE_REF(DebugPass) -} // namespace Seele diff --git a/src/Engine/Graphics/RenderPass/DepthCullingPass.cpp b/src/Engine/Graphics/RenderPass/DepthCullingPass.cpp index 714a71a..c724db4 100644 --- a/src/Engine/Graphics/RenderPass/DepthCullingPass.cpp +++ b/src/Engine/Graphics/RenderPass/DepthCullingPass.cpp @@ -4,9 +4,6 @@ using namespace Seele; -extern bool usePositionOnly; -extern bool useDepthCulling; - DepthCullingPass::DepthCullingPass(Gfx::PGraphics graphics, PScene scene) : RenderPass(graphics, scene) { depthAttachmentLayout = graphics->createDescriptorLayout("pDepthAttachment"); depthAttachmentLayout->addDescriptorBinding(Gfx::DescriptorBinding{ @@ -68,13 +65,15 @@ DepthCullingPass::DepthCullingPass(Gfx::PGraphics graphics, PScene scene) : Rend DepthCullingPass::~DepthCullingPass() {} -void DepthCullingPass::beginFrame(const Component::Camera& cam) { RenderPass::beginFrame(cam); } +void DepthCullingPass::beginFrame(const Component::Camera& cam) { + RenderPass::beginFrame(cam); +} void DepthCullingPass::render() { query->beginQuery(); depthAttachment.getTexture()->changeLayout(Gfx::SE_IMAGE_LAYOUT_GENERAL, Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, - Gfx::SE_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, Gfx::SE_ACCESS_TRANSFER_READ_BIT, - Gfx::SE_PIPELINE_STAGE_TRANSFER_BIT); + Gfx::SE_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, Gfx::SE_ACCESS_SHADER_READ_BIT, + Gfx::SE_PIPELINE_STAGE_COMPUTE_SHADER_BIT); Gfx::PDescriptorSet set = depthAttachmentLayout->allocateDescriptorSet(); set->updateTexture(0, Gfx::PTexture2D(depthAttachment.getTexture())); @@ -123,8 +122,8 @@ void DepthCullingPass::render() { Array commands; Gfx::ShaderPermutation permutation = graphics->getShaderCompiler()->getTemplate("DepthPass"); - permutation.setPositionOnly(usePositionOnly); - permutation.setDepthCulling(useDepthCulling); + permutation.setPositionOnly(getGlobals().usePositionOnly); + permutation.setDepthCulling(getGlobals().useDepthCulling); for (VertexData* vertexData : VertexData::getList()) { permutation.setVertexData(vertexData->getTypeName()); vertexData->getInstanceDataSet()->updateBuffer(6, cullingBuffer); @@ -148,10 +147,6 @@ void DepthCullingPass::render() { .fragmentShader = collection->fragmentShader, .renderPass = renderPass, .pipelineLayout = collection->pipelineLayout, - .multisampleState = - { - .samples = viewport->getSamples(), - }, .colorBlend = { .attachmentCount = 1, @@ -165,10 +160,6 @@ void DepthCullingPass::render() { .fragmentShader = collection->fragmentShader, .renderPass = renderPass, .pipelineLayout = collection->pipelineLayout, - .multisampleState = - { - .samples = viewport->getSamples(), - }, .colorBlend = { .attachmentCount = 1, diff --git a/src/Engine/Graphics/RenderPass/LightCullingPass.cpp b/src/Engine/Graphics/RenderPass/LightCullingPass.cpp index 6a30397..703b554 100644 --- a/src/Engine/Graphics/RenderPass/LightCullingPass.cpp +++ b/src/Engine/Graphics/RenderPass/LightCullingPass.cpp @@ -8,8 +8,6 @@ using namespace Seele; -extern bool useLightCulling; - LightCullingPass::LightCullingPass(Gfx::PGraphics graphics, PScene scene) : RenderPass(graphics, scene) {} LightCullingPass::~LightCullingPass() {} @@ -25,7 +23,13 @@ void LightCullingPass::beginFrame(const Component::Camera& cam) { Gfx::SE_PIPELINE_STAGE_TRANSFER_BIT); uint32 reset = 0; ShaderBufferCreateInfo counterReset = { - .sourceData = {.size = sizeof(uint32), .data = (uint8*)&reset, .owner = Gfx::QueueType::COMPUTE}}; + .sourceData = + { + .size = sizeof(uint32), + .data = (uint8*)&reset, + .owner = Gfx::QueueType::COMPUTE, + }, + }; oLightIndexCounter->rotateBuffer(sizeof(uint32)); oLightIndexCounter->updateContents(counterReset); tLightIndexCounter->rotateBuffer(sizeof(uint32)); @@ -42,7 +46,10 @@ void LightCullingPass::beginFrame(const Component::Camera& cam) { void LightCullingPass::render() { query->beginQuery(); timestamps->write(Gfx::SE_PIPELINE_STAGE_TOP_OF_PIPE_BIT, "LIGHTCULL"); - depthAttachment->transferOwnership(Gfx::QueueType::COMPUTE); + oLightGrid->pipelineBarrier(Gfx::SE_ACCESS_SHADER_READ_BIT, Gfx::SE_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, Gfx::SE_ACCESS_SHADER_WRITE_BIT, + Gfx::SE_PIPELINE_STAGE_COMPUTE_SHADER_BIT); + tLightGrid->pipelineBarrier(Gfx::SE_ACCESS_SHADER_READ_BIT, Gfx::SE_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, Gfx::SE_ACCESS_SHADER_WRITE_BIT, + Gfx::SE_PIPELINE_STAGE_COMPUTE_SHADER_BIT); cullingDescriptorSet->updateTexture(0, depthAttachment); cullingDescriptorSet->updateBuffer(1, oLightIndexCounter); cullingDescriptorSet->updateBuffer(2, tLightIndexCounter); @@ -52,7 +59,7 @@ void LightCullingPass::render() { cullingDescriptorSet->updateTexture(6, Gfx::PTexture2D(tLightGrid)); cullingDescriptorSet->writeChanges(); Gfx::OComputeCommand computeCommand = graphics->createComputeCommand("CullingCommand"); - if (useLightCulling) { + if (getGlobals().useLightCulling) { computeCommand->bindPipeline(cullingEnabledPipeline); } else { computeCommand->bindPipeline(cullingPipeline); @@ -84,11 +91,17 @@ void LightCullingPass::publishOutputs() { dispatchParams.numThreadGroups = numThreadGroups; dispatchParams.numThreads = numThreadGroups * glm::uvec3(BLOCK_SIZE, BLOCK_SIZE, 1); dispatchParamsBuffer = graphics->createUniformBuffer(UniformBufferCreateInfo{ - .sourceData = {.size = sizeof(DispatchParams), .data = (uint8*)&dispatchParams, .owner = Gfx::QueueType::COMPUTE}, + .sourceData = + { + .size = sizeof(DispatchParams), + .data = (uint8*)&dispatchParams, + .owner = Gfx::QueueType::COMPUTE, + }, .dynamic = false, .name = "DispatchParams", }); - + dispatchParamsBuffer->pipelineBarrier(Gfx::SE_ACCESS_TRANSFER_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_TRANSFER_BIT, + Gfx::SE_ACCESS_UNIFORM_READ_BIT, Gfx::SE_PIPELINE_STAGE_COMPUTE_SHADER_BIT); dispatchParamsSet = dispatchParamsLayout->allocateDescriptorSet(); dispatchParamsSet->updateBuffer(0, dispatchParamsBuffer); dispatchParamsSet->updateBuffer(1, frustumBuffer); @@ -209,6 +222,10 @@ void LightCullingPass::publishOutputs() { }; oLightGrid = graphics->createTexture2D(textureInfo); tLightGrid = graphics->createTexture2D(textureInfo); + oLightGrid->changeLayout(Gfx::SE_IMAGE_LAYOUT_GENERAL, Gfx::SE_ACCESS_NONE, Gfx::SE_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + Gfx::SE_ACCESS_SHADER_READ_BIT | Gfx::SE_ACCESS_SHADER_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_COMPUTE_SHADER_BIT); + tLightGrid->changeLayout(Gfx::SE_IMAGE_LAYOUT_GENERAL, Gfx::SE_ACCESS_NONE, Gfx::SE_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + Gfx::SE_ACCESS_SHADER_READ_BIT | Gfx::SE_ACCESS_SHADER_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_COMPUTE_SHADER_BIT); resources->registerTextureOutput("LIGHTCULLING_OLIGHTGRID", Gfx::PTexture2D(oLightGrid)); resources->registerTextureOutput("LIGHTCULLING_TLIGHTGRID", Gfx::PTexture2D(tLightGrid)); @@ -230,6 +247,8 @@ void LightCullingPass::setupFrustums() { glm::uvec3 numThreadGroups = glm::ceil(glm::vec3(numThreads.x / (float)BLOCK_SIZE, numThreads.y / (float)BLOCK_SIZE, 1)); RenderPass::beginFrame(Component::Camera()); + viewParamsBuffer->pipelineBarrier(Gfx::SE_ACCESS_TRANSFER_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_TRANSFER_BIT, + Gfx::SE_ACCESS_UNIFORM_READ_BIT, Gfx::SE_PIPELINE_STAGE_COMPUTE_SHADER_BIT); dispatchParams.numThreads = numThreads; dispatchParams.numThreadGroups = numThreadGroups; diff --git a/src/Engine/Graphics/RenderPass/LightCullingPass.h b/src/Engine/Graphics/RenderPass/LightCullingPass.h index 314e1ea..191c38f 100644 --- a/src/Engine/Graphics/RenderPass/LightCullingPass.h +++ b/src/Engine/Graphics/RenderPass/LightCullingPass.h @@ -41,7 +41,6 @@ class LightCullingPass : public RenderPass { Gfx::OShaderBuffer frustumBuffer; Gfx::OUniformBuffer dispatchParamsBuffer; - Gfx::OUniformBuffer viewParamsBuffer; Gfx::ODescriptorLayout dispatchParamsLayout; Gfx::PDescriptorSet dispatchParamsSet; Gfx::OComputeShader frustumShader; diff --git a/src/Engine/Graphics/RenderPass/RenderPass.cpp b/src/Engine/Graphics/RenderPass/RenderPass.cpp index e1a81f7..13366af 100644 --- a/src/Engine/Graphics/RenderPass/RenderPass.cpp +++ b/src/Engine/Graphics/RenderPass/RenderPass.cpp @@ -40,7 +40,9 @@ void RenderPass::beginFrame(const Component::Camera& cam) { viewParamsBuffer->rotateBuffer(sizeof(ViewParameter)); viewParamsBuffer->updateContents(uniformUpdate); viewParamsBuffer->pipelineBarrier(Gfx::SE_ACCESS_TRANSFER_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_TRANSFER_BIT, - Gfx::SE_ACCESS_MEMORY_READ_BIT, Gfx::SE_PIPELINE_STAGE_ALL_COMMANDS_BIT); + Gfx::SE_ACCESS_UNIFORM_READ_BIT, + Gfx::SE_PIPELINE_STAGE_TASK_SHADER_BIT_EXT | Gfx::SE_PIPELINE_STAGE_MESH_SHADER_BIT_EXT | + Gfx::SE_PIPELINE_STAGE_VERTEX_SHADER_BIT | Gfx::SE_PIPELINE_STAGE_COMPUTE_SHADER_BIT); viewParamsLayout->reset(); viewParamsSet = viewParamsLayout->allocateDescriptorSet(); viewParamsSet->updateBuffer(0, viewParamsBuffer); diff --git a/src/Engine/Graphics/RenderPass/SkyboxRenderPass.cpp b/src/Engine/Graphics/RenderPass/SkyboxRenderPass.cpp deleted file mode 100644 index 24be324..0000000 --- a/src/Engine/Graphics/RenderPass/SkyboxRenderPass.cpp +++ /dev/null @@ -1,176 +0,0 @@ -#include "SkyboxRenderPass.h" -#include "Asset/AssetRegistry.h" -#include "Graphics/Command.h" -#include "Graphics/Graphics.h" - -using namespace Seele; - -SkyboxRenderPass::SkyboxRenderPass(Gfx::PGraphics graphics, PScene scene) : RenderPass(graphics, scene) { - skybox = Seele::Component::Skybox{ - .day = AssetRegistry::findTexture("", "skyboxsun5deg_tn")->getTexture().cast(), - .night = AssetRegistry::findTexture("", "skyboxsun5deg_tn")->getTexture().cast(), - .fogColor = Vector(0.1, 0.1, 0.8), - .blendFactor = 0, - }; -} -SkyboxRenderPass::~SkyboxRenderPass() {} - -void SkyboxRenderPass::beginFrame(const Component::Camera& cam) { - RenderPass::beginFrame(cam); - - skyboxDataLayout->reset(); - textureLayout->reset(); - skyboxData.transformMatrix = glm::rotate(skyboxData.transformMatrix, (float)(Gfx::getCurrentFrameDelta()), Vector(0, 1, 0)); - skyboxBuffer->updateContents(DataSource{ - .size = sizeof(SkyboxData), - .data = (uint8*)&skyboxData, - }); - skyboxDataSet = skyboxDataLayout->allocateDescriptorSet(); - skyboxDataSet->updateBuffer(0, skyboxBuffer); - skyboxDataSet->writeChanges(); - skyboxBuffer->pipelineBarrier(Gfx::SE_ACCESS_TRANSFER_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_TRANSFER_BIT, Gfx::SE_ACCESS_MEMORY_READ_BIT, - Gfx::SE_PIPELINE_STAGE_VERTEX_SHADER_BIT); - textureSet = textureLayout->allocateDescriptorSet(); - textureSet->updateTexture(0, skybox.day); - textureSet->updateTexture(1, skybox.night); - textureSet->updateSampler(2, skyboxSampler); - textureSet->writeChanges(); -} - -void SkyboxRenderPass::render() { - colorAttachment.getTexture()->pipelineBarrier( - Gfx::SE_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - Gfx::SE_ACCESS_COLOR_ATTACHMENT_READ_BIT, Gfx::SE_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT); - depthAttachment.getTexture()->pipelineBarrier( - Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, - Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT, Gfx::SE_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT); - graphics->beginRenderPass(renderPass); - Gfx::ORenderCommand renderCommand = graphics->createRenderCommand("SkyboxRender"); - renderCommand->setViewport(viewport); - renderCommand->bindPipeline(pipeline); - renderCommand->bindDescriptor({viewParamsSet, skyboxDataSet, textureSet}); - renderCommand->draw(36, 1, 0, 0); - Array commands; - commands.add(std::move(renderCommand)); - graphics->executeCommands(std::move(commands)); - graphics->endRenderPass(); -} - -void SkyboxRenderPass::endFrame() {} - -void SkyboxRenderPass::publishOutputs() { - skyboxDataLayout = graphics->createDescriptorLayout("pSkyboxData"); - skyboxDataLayout->addDescriptorBinding(Gfx::DescriptorBinding{ - .binding = 0, - .descriptorType = Gfx::SE_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - }); - skyboxDataLayout->create(); - textureLayout = graphics->createDescriptorLayout("pSkyboxTextures"); - textureLayout->addDescriptorBinding(Gfx::DescriptorBinding{ - .binding = 0, - .descriptorType = Gfx::SE_DESCRIPTOR_TYPE_SAMPLED_IMAGE, - }); - textureLayout->addDescriptorBinding(Gfx::DescriptorBinding{ - .binding = 1, - .descriptorType = Gfx::SE_DESCRIPTOR_TYPE_SAMPLED_IMAGE, - }); - textureLayout->addDescriptorBinding(Gfx::DescriptorBinding{ - .binding = 2, - .descriptorType = Gfx::SE_DESCRIPTOR_TYPE_SAMPLER, - }); - textureLayout->create(); - - skyboxSampler = graphics->createSampler({}); -} - -void SkyboxRenderPass::createRenderPass() { - colorAttachment = resources->requestRenderTarget("BASEPASS_COLOR"); - colorAttachment.setLoadOp(Gfx::SE_ATTACHMENT_LOAD_OP_LOAD); - colorAttachment.setInitialLayout(Gfx::SE_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); - colorAttachment.setInitialLayout(Gfx::SE_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); - - depthAttachment = resources->requestRenderTarget("BASEPASS_DEPTH"); - depthAttachment.setLoadOp(Gfx::SE_ATTACHMENT_LOAD_OP_LOAD); - depthAttachment.setInitialLayout(Gfx::SE_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); - depthAttachment.setFinalLayout(Gfx::SE_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); - Gfx::RenderTargetLayout layout = Gfx::RenderTargetLayout{ - .colorAttachments = {colorAttachment}, - .depthAttachment = depthAttachment, - }; - Array dependency = { - { - .srcSubpass = ~0U, - .dstSubpass = 0, - .srcStage = Gfx::SE_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | Gfx::SE_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, - .dstStage = Gfx::SE_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | Gfx::SE_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, - .srcAccess = Gfx::SE_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | - Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, - .dstAccess = Gfx::SE_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | - Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, - }, - { - .srcSubpass = 0, - .dstSubpass = ~0U, - .srcStage = Gfx::SE_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | Gfx::SE_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, - .dstStage = Gfx::SE_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | Gfx::SE_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, - .srcAccess = Gfx::SE_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | - Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, - .dstAccess = Gfx::SE_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | - Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, - }, - }; - renderPass = graphics->createRenderPass(std::move(layout), dependency, viewport); - - skyboxData.transformMatrix = Matrix4(1); - skyboxData.fogColor = skybox.fogColor; - skyboxData.blendFactor = skybox.blendFactor; - - skyboxBuffer = graphics->createUniformBuffer(UniformBufferCreateInfo{ - .sourceData = - { - .size = sizeof(SkyboxData), - .data = (uint8*)&skyboxData, - }, - .dynamic = true, - }); - - pipelineLayout = graphics->createPipelineLayout("SkyboxLayout"); - pipelineLayout->addDescriptorLayout(viewParamsLayout); - pipelineLayout->addDescriptorLayout(skyboxDataLayout); - pipelineLayout->addDescriptorLayout(textureLayout); - - ShaderCreateInfo createInfo = { - .name = "SkyboxVertex", - .mainModule = "Skybox", - .entryPoint = "vertexMain", - .rootSignature = pipelineLayout, - }; - vertexShader = graphics->createVertexShader(createInfo); - - createInfo.name = "SkyboxFragment"; - createInfo.entryPoint = "fragmentMain"; - fragmentShader = graphics->createFragmentShader(createInfo); - - pipelineLayout->create(); - - Gfx::LegacyPipelineCreateInfo gfxInfo = { - .topology = Gfx::SE_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, - .vertexShader = vertexShader, - .fragmentShader = fragmentShader, - .renderPass = renderPass, - .pipelineLayout = pipelineLayout, - .multisampleState = - { - .samples = viewport->getSamples(), - }, - .rasterizationState = - { - .polygonMode = Gfx::SE_POLYGON_MODE_FILL, - }, - .colorBlend = - { - .attachmentCount = 1, - }, - }; - pipeline = graphics->createGraphicsPipeline(std::move(gfxInfo)); -} diff --git a/src/Engine/Graphics/RenderPass/SkyboxRenderPass.h b/src/Engine/Graphics/RenderPass/SkyboxRenderPass.h deleted file mode 100644 index 024c3b7..0000000 --- a/src/Engine/Graphics/RenderPass/SkyboxRenderPass.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once -#include "Component/Skybox.h" -#include "Graphics/Shader.h" -#include "RenderPass.h" - - -namespace Seele { -class SkyboxRenderPass : public RenderPass { - public: - SkyboxRenderPass(Gfx::PGraphics graphics, PScene scene); - SkyboxRenderPass(SkyboxRenderPass&&) = default; - SkyboxRenderPass& operator=(SkyboxRenderPass&&) = default; - virtual ~SkyboxRenderPass(); - virtual void beginFrame(const Component::Camera& cam) override; - virtual void render() override; - virtual void endFrame() override; - virtual void publishOutputs() override; - virtual void createRenderPass() override; - - private: - Gfx::RenderTargetAttachment colorAttachment; - Gfx::RenderTargetAttachment depthAttachment; - Gfx::ODescriptorLayout skyboxDataLayout; - Gfx::PDescriptorSet skyboxDataSet; - Gfx::ODescriptorLayout textureLayout; - Gfx::PDescriptorSet textureSet; - Gfx::OVertexShader vertexShader; - Gfx::OFragmentShader fragmentShader; - Gfx::OPipelineLayout pipelineLayout; - Gfx::PGraphicsPipeline pipeline; - Gfx::OSampler skyboxSampler; - struct SkyboxData { - Matrix4 transformMatrix; - Vector fogColor; - float blendFactor; - } skyboxData; - Gfx::OUniformBuffer skyboxBuffer; - Component::Skybox skybox; -}; -DEFINE_REF(SkyboxRenderPass) -} // namespace Seele diff --git a/src/Engine/Graphics/RenderPass/VisibilityPass.cpp b/src/Engine/Graphics/RenderPass/VisibilityPass.cpp index 316992e..e7dc168 100644 --- a/src/Engine/Graphics/RenderPass/VisibilityPass.cpp +++ b/src/Engine/Graphics/RenderPass/VisibilityPass.cpp @@ -3,8 +3,6 @@ using namespace Seele; -extern bool resetVisibility; - VisibilityPass::VisibilityPass(Gfx::PGraphics graphics, PScene scene) : RenderPass(graphics, scene) {} VisibilityPass::~VisibilityPass() {} diff --git a/src/Engine/Graphics/Resources.h b/src/Engine/Graphics/Resources.h index 04e32de..050aedd 100644 --- a/src/Engine/Graphics/Resources.h +++ b/src/Engine/Graphics/Resources.h @@ -3,11 +3,6 @@ #include "Enums.h" #include "Math/Math.h" - -#ifndef ENABLE_VALIDATION -#define ENABLE_VALIDATION 1 -#endif - namespace Seele { DECLARE_REF(Material) namespace Gfx { diff --git a/src/Engine/Graphics/VertexData.cpp b/src/Engine/Graphics/VertexData.cpp index 39568b6..00f3d04 100644 --- a/src/Engine/Graphics/VertexData.cpp +++ b/src/Engine/Graphics/VertexData.cpp @@ -211,198 +211,195 @@ void VertexData::createDescriptors() { } void VertexData::loadMesh(MeshId id, Array loadedIndices, Array loadedMeshlets) { - std::unique_lock l(vertexDataLock); - meshlets.reserve(meshlets.size() + loadedMeshlets.size()); - vertexIndices.reserve(vertexIndices.size() + loadedMeshlets.size() * Gfx::numVerticesPerMeshlet); - primitiveIndices.reserve(primitiveIndices.size() + loadedMeshlets.size() * Gfx::numPrimitivesPerMeshlet * 3); - uint32 meshletOffset = meshlets.size(); - AABB meshAABB; - for (uint32 i = 0; i < loadedMeshlets.size(); ++i) { - Meshlet& m = loadedMeshlets[i]; - meshAABB = meshAABB.combine(m.boundingBox); - uint32 vertexOffset = vertexIndices.size(); - vertexIndices.resize(vertexOffset + m.numVertices); - std::memcpy(vertexIndices.data() + vertexOffset, m.uniqueVertices, m.numVertices * sizeof(uint32)); - uint32 primitiveOffset = primitiveIndices.size(); - primitiveIndices.resize(primitiveOffset + (m.numPrimitives * 3)); - std::memcpy(primitiveIndices.data() + primitiveOffset, m.primitiveLayout, m.numPrimitives * 3 * sizeof(uint8)); - meshlets.add(MeshletDescription{ - .bounding = m.boundingBox, //.toSphere(), - .vertexCount = m.numVertices, - .primitiveCount = m.numPrimitives, - .vertexOffset = vertexOffset, - .primitiveOffset = primitiveOffset, - .color = Vector((float)rand() / RAND_MAX, (float)rand() / RAND_MAX, (float)rand() / RAND_MAX), - .indicesOffset = (uint32)meshOffsets[id], - }); - } - meshData[id] = MeshData{ - .bounding = meshAABB, //.toSphere(), - .numMeshlets = (uint32)loadedMeshlets.size(), - .meshletOffset = meshletOffset, - .firstIndex = (uint32)indices.size(), - .numIndices = (uint32)loadedIndices.size(), - }; + std::unique_lock l(vertexDataLock); + meshlets.reserve(meshlets.size() + loadedMeshlets.size()); + vertexIndices.reserve(vertexIndices.size() + loadedMeshlets.size() * Gfx::numVerticesPerMeshlet); + primitiveIndices.reserve(primitiveIndices.size() + loadedMeshlets.size() * Gfx::numPrimitivesPerMeshlet * 3); + uint32 meshletOffset = meshlets.size(); + AABB meshAABB; + for (uint32 i = 0; i < loadedMeshlets.size(); ++i) { + Meshlet& m = loadedMeshlets[i]; + meshAABB = meshAABB.combine(m.boundingBox); + uint32 vertexOffset = vertexIndices.size(); + vertexIndices.resize(vertexOffset + m.numVertices); + std::memcpy(vertexIndices.data() + vertexOffset, m.uniqueVertices, m.numVertices * sizeof(uint32)); + uint32 primitiveOffset = primitiveIndices.size(); + primitiveIndices.resize(primitiveOffset + (m.numPrimitives * 3)); + std::memcpy(primitiveIndices.data() + primitiveOffset, m.primitiveLayout, m.numPrimitives * 3 * sizeof(uint8)); + meshlets.add(MeshletDescription{ + .bounding = m.boundingBox, //.toSphere(), + .vertexCount = m.numVertices, + .primitiveCount = m.numPrimitives, + .vertexOffset = vertexOffset, + .primitiveOffset = primitiveOffset, + .color = Vector((float)rand() / RAND_MAX, (float)rand() / RAND_MAX, (float)rand() / RAND_MAX), + .indicesOffset = (uint32)meshOffsets[id], + }); + } + meshData[id] = MeshData{ + .bounding = meshAABB, //.toSphere(), + .numMeshlets = (uint32)loadedMeshlets.size(), + .meshletOffset = meshletOffset, + .firstIndex = (uint32)indices.size(), + .numIndices = (uint32)loadedIndices.size(), + }; - indices.resize(indices.size() + loadedIndices.size()); - std::memcpy(indices.data() + meshData[id].firstIndex, loadedIndices.data(), loadedIndices.size() * sizeof(uint32)); + indices.resize(indices.size() + loadedIndices.size()); + std::memcpy(indices.data() + meshData[id].firstIndex, loadedIndices.data(), loadedIndices.size() * sizeof(uint32)); } void VertexData::commitMeshes() { - indexBuffer = graphics->createIndexBuffer(IndexBufferCreateInfo{ - .sourceData = - { - .size = sizeof(uint32) * indices.size(), - .data = (uint8*)indices.data(), - }, - .indexType = Gfx::SE_INDEX_TYPE_UINT32, - .name = "IndexBuffer", - }); - meshletBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{ - .sourceData = - { - .size = sizeof(MeshletDescription) * meshlets.size(), - .data = (uint8*)meshlets.data(), - }, - .numElements = meshlets.size(), - .dynamic = false, - .name = "MeshletBuffer", - }); - vertexIndicesBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{ - .sourceData = - { - .size = sizeof(uint32) * vertexIndices.size(), - .data = (uint8*)vertexIndices.data(), - }, - .numElements = vertexIndices.size(), - .dynamic = false, - .name = "VertexIndicesBuffer", - }); + indexBuffer = graphics->createIndexBuffer(IndexBufferCreateInfo{ + .sourceData = + { + .size = sizeof(uint32) * indices.size(), + .data = (uint8*)indices.data(), + }, + .indexType = Gfx::SE_INDEX_TYPE_UINT32, + .name = "IndexBuffer", + }); + meshletBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{ + .sourceData = + { + .size = sizeof(MeshletDescription) * meshlets.size(), + .data = (uint8*)meshlets.data(), + }, + .numElements = meshlets.size(), + .dynamic = false, + .name = "MeshletBuffer", + }); + vertexIndicesBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{ + .sourceData = + { + .size = sizeof(uint32) * vertexIndices.size(), + .data = (uint8*)vertexIndices.data(), + }, + .numElements = vertexIndices.size(), + .dynamic = false, + .name = "VertexIndicesBuffer", + }); - primitiveIndicesBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{ - .sourceData = - { - .size = sizeof(uint8) * primitiveIndices.size(), - .data = (uint8*)primitiveIndices.data(), - }, - .numElements = primitiveIndices.size(), - .dynamic = false, - .name = "PrimitiveIndicesBuffer", - }); + primitiveIndicesBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{ + .sourceData = + { + .size = sizeof(uint8) * primitiveIndices.size(), + .data = (uint8*)primitiveIndices.data(), + }, + .numElements = primitiveIndices.size(), + .dynamic = false, + .name = "PrimitiveIndicesBuffer", + }); } MeshId VertexData::allocateVertexData(uint64 numVertices) { - std::unique_lock l(vertexDataLock); - MeshId res{idCounter++}; - meshOffsets.add(head); - meshVertexCounts.add(numVertices); - meshData.add({}); - head += numVertices; - if (head > verticesAllocated) { - verticesAllocated = std::max(head, verticesAllocated + NUM_DEFAULT_ELEMENTS); - resizeBuffers(); - } - return res; + std::unique_lock l(vertexDataLock); + MeshId res{idCounter++}; + meshOffsets.add(head); + meshVertexCounts.add(numVertices); + meshData.add({}); + head += numVertices; + if (head > verticesAllocated) { + verticesAllocated = std::max(head, verticesAllocated + NUM_DEFAULT_ELEMENTS); + resizeBuffers(); + } + return res; } -uint64 VertexData::getMeshOffset(MeshId id) { - return meshOffsets[id]; } +uint64 VertexData::getMeshOffset(MeshId id) { return meshOffsets[id]; } -uint64 VertexData::getMeshVertexCount(MeshId id) { - return meshVertexCounts[id]; } +uint64 VertexData::getMeshVertexCount(MeshId id) { return meshVertexCounts[id]; } List vertexDataList; -List VertexData::getList() { - return vertexDataList; } +List VertexData::getList() { return vertexDataList; } VertexData* VertexData::findByTypeName(std::string name) { - for (auto vd : vertexDataList) { - if (vd->getTypeName() == name) { - return vd; - } + for (auto vd : vertexDataList) { + if (vd->getTypeName() == name) { + return vd; } - return nullptr; + } + return nullptr; } void VertexData::init(Gfx::PGraphics _graphics) { - graphics = _graphics; - verticesAllocated = NUM_DEFAULT_ELEMENTS; - instanceDataLayout = graphics->createDescriptorLayout("pScene"); + graphics = _graphics; + verticesAllocated = NUM_DEFAULT_ELEMENTS; + instanceDataLayout = graphics->createDescriptorLayout("pScene"); - // instanceData - instanceDataLayout->addDescriptorBinding(Gfx::DescriptorBinding{ - .binding = 0, - .descriptorType = Gfx::SE_DESCRIPTOR_TYPE_STORAGE_BUFFER, - }); - // meshData - instanceDataLayout->addDescriptorBinding(Gfx::DescriptorBinding{ - .binding = 1, - .descriptorType = Gfx::SE_DESCRIPTOR_TYPE_STORAGE_BUFFER, - }); - // meshletData - instanceDataLayout->addDescriptorBinding( - Gfx::DescriptorBinding{.binding = 2, .descriptorType = Gfx::SE_DESCRIPTOR_TYPE_STORAGE_BUFFER}); - // primitiveIndices - instanceDataLayout->addDescriptorBinding( - Gfx::DescriptorBinding{.binding = 3, .descriptorType = Gfx::SE_DESCRIPTOR_TYPE_STORAGE_BUFFER}); - // vertexIndices - instanceDataLayout->addDescriptorBinding( - Gfx::DescriptorBinding{.binding = 4, .descriptorType = Gfx::SE_DESCRIPTOR_TYPE_STORAGE_BUFFER}); - // cullingOffset - instanceDataLayout->addDescriptorBinding( - Gfx::DescriptorBinding{.binding = 5, .descriptorType = Gfx::SE_DESCRIPTOR_TYPE_STORAGE_BUFFER}); - // cullingInfos - instanceDataLayout->addDescriptorBinding( - Gfx::DescriptorBinding{.binding = 6, .descriptorType = Gfx::SE_DESCRIPTOR_TYPE_STORAGE_BUFFER}); + // instanceData + instanceDataLayout->addDescriptorBinding(Gfx::DescriptorBinding{ + .binding = 0, + .descriptorType = Gfx::SE_DESCRIPTOR_TYPE_STORAGE_BUFFER, + }); + // meshData + instanceDataLayout->addDescriptorBinding(Gfx::DescriptorBinding{ + .binding = 1, + .descriptorType = Gfx::SE_DESCRIPTOR_TYPE_STORAGE_BUFFER, + }); + // meshletData + instanceDataLayout->addDescriptorBinding( + Gfx::DescriptorBinding{.binding = 2, .descriptorType = Gfx::SE_DESCRIPTOR_TYPE_STORAGE_BUFFER}); + // primitiveIndices + instanceDataLayout->addDescriptorBinding( + Gfx::DescriptorBinding{.binding = 3, .descriptorType = Gfx::SE_DESCRIPTOR_TYPE_STORAGE_BUFFER}); + // vertexIndices + instanceDataLayout->addDescriptorBinding( + Gfx::DescriptorBinding{.binding = 4, .descriptorType = Gfx::SE_DESCRIPTOR_TYPE_STORAGE_BUFFER}); + // cullingOffset + instanceDataLayout->addDescriptorBinding( + Gfx::DescriptorBinding{.binding = 5, .descriptorType = Gfx::SE_DESCRIPTOR_TYPE_STORAGE_BUFFER}); + // cullingInfos + instanceDataLayout->addDescriptorBinding( + Gfx::DescriptorBinding{.binding = 6, .descriptorType = Gfx::SE_DESCRIPTOR_TYPE_STORAGE_BUFFER}); - instanceDataLayout->create(); + instanceDataLayout->create(); - cullingOffsetBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{ - .dynamic = true, - .name = "MeshletOffset", - }); - instanceBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{ - .dynamic = true, - .name = "InstanceBuffer", - }); - instanceMeshDataBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{ - .dynamic = true, - .name = "MeshDataBuffer", - }); + cullingOffsetBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{ + .dynamic = true, + .name = "MeshletOffset", + }); + instanceBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{ + .dynamic = true, + .name = "InstanceBuffer", + }); + instanceMeshDataBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{ + .dynamic = true, + .name = "MeshDataBuffer", + }); - transparentInstanceDataBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{ - .dynamic = true, - .name = "TransparentInstanceBuffer", - }); - transparentMeshDataBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{ - .dynamic = true, - .name = "TransparentMeshBuffer", - }); + transparentInstanceDataBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{ + .dynamic = true, + .name = "TransparentInstanceBuffer", + }); + transparentMeshDataBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{ + .dynamic = true, + .name = "TransparentMeshBuffer", + }); - resizeBuffers(); - graphics->getShaderCompiler()->registerVertexData(this); + resizeBuffers(); + graphics->getShaderCompiler()->registerVertexData(this); } void VertexData::destroy() { - instanceBuffer = nullptr; - instanceMeshDataBuffer = nullptr; - instanceDataLayout = nullptr; - meshletBuffer = nullptr; - vertexIndicesBuffer = nullptr; - primitiveIndicesBuffer = nullptr; - indexBuffer = nullptr; - meshData.clear(); - materialData.clear(); + instanceBuffer = nullptr; + instanceMeshDataBuffer = nullptr; + instanceDataLayout = nullptr; + meshletBuffer = nullptr; + vertexIndicesBuffer = nullptr; + primitiveIndicesBuffer = nullptr; + indexBuffer = nullptr; + meshData.clear(); + materialData.clear(); } VertexData::CullingMapping VertexData::getCullingMapping(entt::entity id, uint32 meshIndex, uint32 numMeshlets) { - MeshMapping key = MeshMapping{.id = id, .meshId = meshIndex}; - if (!instanceIdMap.contains(key)) { - instanceIdMap[key] = CullingMapping{.instanceId = instanceCount++, .cullingOffset = uint32(meshletCount)}; - meshletCount += numMeshlets; - } - return instanceIdMap[key]; + MeshMapping key = MeshMapping{.id = id, .meshId = meshIndex}; + if (!instanceIdMap.contains(key)) { + instanceIdMap[key] = CullingMapping{.instanceId = instanceCount++, .cullingOffset = uint32(meshletCount)}; + meshletCount += numMeshlets; + } + return instanceIdMap[key]; } VertexData::VertexData() : idCounter(0), head(0), verticesAllocated(0), dirty(false) {} diff --git a/src/Engine/Graphics/Vulkan/Buffer.cpp b/src/Engine/Graphics/Vulkan/Buffer.cpp index b5db025..54ca8ec 100644 --- a/src/Engine/Graphics/Vulkan/Buffer.cpp +++ b/src/Engine/Graphics/Vulkan/Buffer.cpp @@ -262,8 +262,20 @@ void Buffer::copyBuffer(uint64 src, uint64 dst) { .offset = 0, .size = buffers[src]->size, }; - vkCmdPipelineBarrier(command->getHandle(), VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1, - &srcBarrier, 0, nullptr); + VkBufferMemoryBarrier clearBarrier = { + .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .buffer = buffers[dst]->buffer, + .offset = 0, + .size = buffers[dst]->size, + }; + VkBufferMemoryBarrier srcBarriers[] = {srcBarrier, clearBarrier}; + vkCmdPipelineBarrier(command->getHandle(), VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 2, + srcBarriers, 0, nullptr); VkBufferCopy region = { .srcOffset = 0, .dstOffset = 0, diff --git a/src/Engine/Graphics/Vulkan/Command.cpp b/src/Engine/Graphics/Vulkan/Command.cpp index ed70621..92731fe 100644 --- a/src/Engine/Graphics/Vulkan/Command.cpp +++ b/src/Engine/Graphics/Vulkan/Command.cpp @@ -141,7 +141,6 @@ void Command::checkFence() { } void Command::waitForCommand(uint32 timeout) { - pool->submitCommands(); if (state == State::Begin) { // is already done return; diff --git a/src/Engine/Graphics/Vulkan/Graphics.cpp b/src/Engine/Graphics/Vulkan/Graphics.cpp index 3d81124..08c3989 100644 --- a/src/Engine/Graphics/Vulkan/Graphics.cpp +++ b/src/Engine/Graphics/Vulkan/Graphics.cpp @@ -46,7 +46,10 @@ void vkCmdDrawMeshTasksIndirectEXT(VkCommandBuffer commandBuffer, VkBuffer buffe } VkResult vkSetDebugUtilsObjectNameEXT(VkDevice device, const VkDebugUtilsObjectNameInfoEXT* pNameInfo) { - return setDebugUtilsObjectName(device, pNameInfo); + if (setDebugUtilsObjectName != nullptr) { + return setDebugUtilsObjectName(device, pNameInfo); + } + return VK_SUCCESS; } VkResult vkCreateAccelerationStructureKHR(VkDevice device, const VkAccelerationStructureCreateInfoKHR* pCreateInfo, @@ -139,7 +142,9 @@ void Graphics::beginRenderPass(Gfx::PRenderPass renderPass) { getGraphicsCommands()->getCommands()->beginRenderPass(rp, framebuffer); } -void Graphics::endRenderPass() { getGraphicsCommands()->getCommands()->endRenderPass(); } +void Graphics::endRenderPass() { + getGraphicsCommands()->getCommands()->endRenderPass(); +} void Graphics::waitDeviceIdle() { vkDeviceWaitIdle(handle); } diff --git a/src/Engine/Graphics/Vulkan/Query.cpp b/src/Engine/Graphics/Vulkan/Query.cpp index f235139..3f31e83 100644 --- a/src/Engine/Graphics/Vulkan/Query.cpp +++ b/src/Engine/Graphics/Vulkan/Query.cpp @@ -150,14 +150,9 @@ Array TimestampQuery::getResults() { tail = (tail + 1) % 512; Array res; for (uint64 i = 0; i < numTimestamps; ++i) { - if (results[i] < lastMeasure) - { - wrapping += 1ull << graphics->getTimestampValidBits(); - } - lastMeasure = results[i]; res.add(Gfx::Timestamp{ .name = pendingTimestamps[firstQuery + i], - .time = std::chrono::nanoseconds(uint64((results[i] + wrapping) * graphics->getTimestampPeriod())), + .time = uint64((results[i] + wrapping) * graphics->getTimestampPeriod()), }); } return res; diff --git a/src/Engine/Graphics/Vulkan/RenderPass.cpp b/src/Engine/Graphics/Vulkan/RenderPass.cpp index 6cb45c8..175918a 100644 --- a/src/Engine/Graphics/Vulkan/RenderPass.cpp +++ b/src/Engine/Graphics/Vulkan/RenderPass.cpp @@ -79,7 +79,7 @@ RenderPass::RenderPass(PGraphics graphics, Gfx::RenderTargetLayout _layout, Arra .pNext = nullptr, .flags = 0, .format = cast(resolveAttachment.getFormat()), - .samples = (VkSampleCountFlagBits)resolveAttachment.getNumSamples(), + .samples = (VkSampleCountFlagBits)resolveAttachment.getTexture()->getNumSamples(), .loadOp = cast(resolveAttachment.getLoadOp()), .storeOp = cast(resolveAttachment.getStoreOp()), .stencilLoadOp = cast(resolveAttachment.getStencilLoadOp()), @@ -143,6 +143,10 @@ RenderPass::RenderPass(PGraphics graphics, Gfx::RenderTargetLayout _layout, Arra .initialLayout = cast(layout.depthResolveAttachment.getInitialLayout()), .finalLayout = cast(layout.depthResolveAttachment.getFinalLayout()), }; + VkClearValue& clearValue = clearValues.add(); + if (attachments.back().loadOp == VK_ATTACHMENT_LOAD_OP_CLEAR) { + clearValue = cast(layout.depthResolveAttachment.clear); + } depthResolveRef = { .sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2, .pNext = nullptr, @@ -214,14 +218,22 @@ uint32 RenderPass::getFramebufferHash() { PTexture2D tex = colorAttachment.getTexture().cast(); description.colorAttachments[description.numColorAttachments++] = tex->getView(); } + for (auto& resolveAttachment : layout.resolveAttachments) { + PTexture2D tex = resolveAttachment.getTexture().cast(); + description.resolveAttachments[description.numResolveAttachments++] = tex->getView(); + } if (layout.depthAttachment.getTexture() != nullptr) { PTexture2D tex = layout.depthAttachment.getTexture().cast(); description.depthAttachment = tex->getView(); } + if (layout.depthResolveAttachment.getTexture() != nullptr) { + PTexture2D tex = layout.depthResolveAttachment.getTexture().cast(); + description.depthResolveAttachment = tex->getView(); + } return CRC::Calculate(&description, sizeof(FramebufferDescription), CRC::CRC_32()); } -void Vulkan::RenderPass::endRenderPass() { +void RenderPass::endRenderPass() { for (auto& inputAttachment : layout.inputAttachments) { PTexture2D tex = inputAttachment.getTexture().cast(); tex->setLayout(inputAttachment.getFinalLayout()); @@ -230,8 +242,16 @@ void Vulkan::RenderPass::endRenderPass() { PTexture2D tex = colorAttachment.getTexture().cast(); tex->setLayout(colorAttachment.getFinalLayout()); } + for (auto& resolveAttachment : layout.resolveAttachments) { + PTexture2D tex = resolveAttachment.getTexture().cast(); + tex->setLayout(resolveAttachment.getFinalLayout()); + } if (layout.depthAttachment.getTexture() != nullptr) { PTexture2D tex = layout.depthAttachment.getTexture().cast(); tex->setLayout(layout.depthAttachment.getFinalLayout()); } + if (layout.depthResolveAttachment.getTexture() != nullptr) { + PTexture2D tex = layout.depthResolveAttachment.getTexture().cast(); + tex->setLayout(layout.depthResolveAttachment.getFinalLayout()); + } } diff --git a/src/Engine/Graphics/Vulkan/Texture.cpp b/src/Engine/Graphics/Vulkan/Texture.cpp index e9e117d..e083371 100644 --- a/src/Engine/Graphics/Vulkan/Texture.cpp +++ b/src/Engine/Graphics/Vulkan/Texture.cpp @@ -137,17 +137,6 @@ TextureHandle::TextureHandle(PGraphics graphics, VkImageViewType viewType, const changeLayout(Gfx::SE_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_READ_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); graphics->getDestructionManager()->queueResourceForDestruction(std::move(stagingAlloc)); - } else { - if (usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) { - changeLayout(Gfx::SE_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, VK_ACCESS_NONE, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, - VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT); - } else if (usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) { - changeLayout(Gfx::SE_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_ACCESS_NONE, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, - VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT); - } else { - changeLayout(Gfx::SE_IMAGE_LAYOUT_GENERAL, VK_ACCESS_NONE, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_ACCESS_MEMORY_WRITE_BIT, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT); - } } VkImageViewCreateInfo viewInfo = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, @@ -331,7 +320,7 @@ void TextureHandle::generateMipmaps() { .aspectMask = aspect, .levelCount = 1, .baseArrayLayer = 0, - .layerCount = 1, + .layerCount = layerCount, }, }; int32 mipWidth = width; diff --git a/src/Engine/Graphics/Vulkan/Window.cpp b/src/Engine/Graphics/Vulkan/Window.cpp index 28cbf7d..0cb161b 100644 --- a/src/Engine/Graphics/Vulkan/Window.cpp +++ b/src/Engine/Graphics/Vulkan/Window.cpp @@ -5,7 +5,6 @@ #include "Resources.h" #include - using namespace Seele; using namespace Seele::Vulkan; @@ -93,9 +92,9 @@ void Window::pollInput() { glfwPollEvents(); } void Window::beginFrame() { imageAvailableFences[currentSemaphoreIndex]->reset(); - vkAcquireNextImageKHR(graphics->getDevice(), swapchain, std::numeric_limits::max(), - imageAvailableSemaphores[currentSemaphoreIndex]->getHandle(), - imageAvailableFences[currentSemaphoreIndex]->getHandle(), ¤tImageIndex); + VK_CHECK(vkAcquireNextImageKHR(graphics->getDevice(), swapchain, std::numeric_limits::max(), + imageAvailableSemaphores[currentSemaphoreIndex]->getHandle(), + imageAvailableFences[currentSemaphoreIndex]->getHandle(), ¤tImageIndex)); imageAvailableFences[currentSemaphoreIndex]->submit(); graphics->getGraphicsCommands()->getCommands()->waitForSemaphore(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, imageAvailableSemaphores[currentSemaphoreIndex]); diff --git a/src/Engine/Math/Vector.cpp b/src/Engine/Math/Vector.cpp index 63d30f4..3e198a9 100644 --- a/src/Engine/Math/Vector.cpp +++ b/src/Engine/Math/Vector.cpp @@ -26,15 +26,20 @@ void from_json(const nlohmann::json& j, Vector& vec) { } std::ostream& operator<<(std::ostream& stream, const Vector2& vector) { - stream << "(" << vector.x << ", " << vector.y << ")"; + stream << "Vec2(" << vector.x << ", " << vector.y << ")"; return stream; } std::ostream& operator<<(std::ostream& stream, const Vector& vector) { - stream << "(" << vector.x << ", " << vector.y << ", " << vector.z << ")"; + stream << "Vec3(" << vector.x << ", " << vector.y << ", " << vector.z << ")"; return stream; } std::ostream& operator<<(std::ostream& stream, const Vector4& vector) { - stream << "(" << vector.x << ", " << vector.y << ", " << vector.z << ", " << vector.w << ")"; + stream << "Vec4(" << vector.x << ", " << vector.y << ", " << vector.z << ", " << vector.w << ")"; + return stream; +} + +std::ostream& operator<<(std::ostream& stream, const Quaternion& vector) { + stream << "Quat(" << vector.w << ", " << vector.x << ", " << vector.y << ", " << vector.z << ")"; return stream; } diff --git a/src/Engine/Math/Vector.h b/src/Engine/Math/Vector.h index ca45b66..aa8d930 100644 --- a/src/Engine/Math/Vector.h +++ b/src/Engine/Math/Vector.h @@ -37,3 +37,4 @@ void from_json(nlohmann::json& j, Vector& vec); std::ostream& operator<<(std::ostream& stream, const Seele::Vector2& vector); std::ostream& operator<<(std::ostream& stream, const Seele::Vector& vector); std::ostream& operator<<(std::ostream& stream, const Seele::Vector4& vector); +std::ostream& operator<<(std::ostream& stream, const Seele::Quaternion& vector); diff --git a/src/Engine/MinimalEngine.h b/src/Engine/MinimalEngine.h index 08e70bf..42b8803 100644 --- a/src/Engine/MinimalEngine.h +++ b/src/Engine/MinimalEngine.h @@ -142,4 +142,13 @@ template class UniquePtr { private: T* handle; }; + +// idk if it should be here? +struct Globals { + bool usePositionOnly = true; + bool useDepthCulling = true; + bool useLightCulling = true; + bool running = true; +}; +Globals& getGlobals(); } // namespace Seele diff --git a/src/Engine/Scene/LightEnvironment.cpp b/src/Engine/Scene/LightEnvironment.cpp index ddf6bd4..66caebb 100644 --- a/src/Engine/Scene/LightEnvironment.cpp +++ b/src/Engine/Scene/LightEnvironment.cpp @@ -60,11 +60,14 @@ void LightEnvironment::addDirectionalLight(Component::DirectionalLight dirLight) void LightEnvironment::addPointLight(Component::PointLight pointLight) { points.add(pointLight); } void LightEnvironment::commit() { - lightEnvBuffer->pipelineBarrier(Gfx::SE_ACCESS_SHADER_READ_BIT, Gfx::SE_PIPELINE_STAGE_ALL_COMMANDS_BIT, + lightEnvBuffer->pipelineBarrier(Gfx::SE_ACCESS_UNIFORM_READ_BIT | Gfx::SE_ACCESS_TRANSFER_WRITE_BIT, + Gfx::SE_PIPELINE_STAGE_ALL_COMMANDS_BIT, Gfx::SE_ACCESS_TRANSFER_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_TRANSFER_BIT); - pointLights->pipelineBarrier(Gfx::SE_ACCESS_SHADER_READ_BIT, Gfx::SE_PIPELINE_STAGE_ALL_COMMANDS_BIT, Gfx::SE_ACCESS_TRANSFER_WRITE_BIT, + pointLights->pipelineBarrier(Gfx::SE_ACCESS_SHADER_READ_BIT | Gfx::SE_ACCESS_TRANSFER_WRITE_BIT, + Gfx::SE_PIPELINE_STAGE_ALL_COMMANDS_BIT, Gfx::SE_ACCESS_TRANSFER_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_TRANSFER_BIT); - directionalLights->pipelineBarrier(Gfx::SE_ACCESS_SHADER_READ_BIT, Gfx::SE_PIPELINE_STAGE_ALL_COMMANDS_BIT, + directionalLights->pipelineBarrier(Gfx::SE_ACCESS_SHADER_READ_BIT | Gfx::SE_ACCESS_TRANSFER_WRITE_BIT, + Gfx::SE_PIPELINE_STAGE_ALL_COMMANDS_BIT, Gfx::SE_ACCESS_TRANSFER_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_TRANSFER_BIT); lightEnv.numDirectionalLights = dirs.size(); lightEnv.numPointLights = points.size(); @@ -87,7 +90,7 @@ void LightEnvironment::commit() { .numElements = points.size(), }); - lightEnvBuffer->pipelineBarrier(Gfx::SE_ACCESS_TRANSFER_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_TRANSFER_BIT, Gfx::SE_ACCESS_SHADER_READ_BIT, + lightEnvBuffer->pipelineBarrier(Gfx::SE_ACCESS_TRANSFER_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_TRANSFER_BIT, Gfx::SE_ACCESS_UNIFORM_READ_BIT, Gfx::SE_PIPELINE_STAGE_ALL_COMMANDS_BIT); pointLights->pipelineBarrier(Gfx::SE_ACCESS_TRANSFER_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_TRANSFER_BIT, Gfx::SE_ACCESS_SHADER_READ_BIT, Gfx::SE_PIPELINE_STAGE_ALL_COMMANDS_BIT); diff --git a/src/Engine/ThreadPool.cpp b/src/Engine/ThreadPool.cpp index d670570..11fc4ab 100644 --- a/src/Engine/ThreadPool.cpp +++ b/src/Engine/ThreadPool.cpp @@ -1,7 +1,12 @@ #include "ThreadPool.h" +#include "MinimalEngine.h" using namespace Seele; +Globals globals; + +Globals& Seele::getGlobals() { return globals; } + ThreadPool::ThreadPool(uint32 numWorkers) { for (uint32 i = 0; i < numWorkers; ++i) { workers.add(std::thread(&ThreadPool::work, this)); diff --git a/src/Engine/ThreadPool.h b/src/Engine/ThreadPool.h index 48e27fd..778f478 100644 --- a/src/Engine/ThreadPool.h +++ b/src/Engine/ThreadPool.h @@ -4,7 +4,6 @@ #include #include - namespace Seele { class ThreadPool { public: diff --git a/src/Engine/Window/GameView.cpp b/src/Engine/Window/GameView.cpp index f0385ca..4562cb4 100644 --- a/src/Engine/Window/GameView.cpp +++ b/src/Engine/Window/GameView.cpp @@ -6,24 +6,19 @@ #include "Graphics/Query.h" #include "Graphics/RenderPass/BasePass.h" #include "Graphics/RenderPass/CachedDepthPass.h" -#include "Graphics/RenderPass/DebugPass.h" #include "Graphics/RenderPass/DepthCullingPass.h" #include "Graphics/RenderPass/LightCullingPass.h" #include "Graphics/RenderPass/RenderGraphResources.h" -#include "Graphics/RenderPass/SkyboxRenderPass.h" #include "Graphics/RenderPass/VisibilityPass.h" #include "System/CameraUpdater.h" #include "System/LightGather.h" #include "System/MeshUpdater.h" #include "Window/Window.h" #include +#include using namespace Seele; -bool usePositionOnly = false; -bool useDepthCulling = false; -bool useLightCulling = false; - GameView::GameView(Gfx::PGraphics graphics, PWindow window, const ViewportCreateInfo& createInfo, std::string dllPath) : View(graphics, window, createInfo, "Game"), scene(new Scene(graphics)), gameInterface(dllPath) { reloadGame(); @@ -32,48 +27,8 @@ GameView::GameView(Gfx::PGraphics graphics, PWindow window, const ViewportCreate renderGraph.addPass(new VisibilityPass(graphics, scene)); renderGraph.addPass(new LightCullingPass(graphics, scene)); renderGraph.addPass(new BasePass(graphics, scene)); - renderGraph.addPass(new DebugPass(graphics, scene)); - // renderGraph.addPass(new SkyboxRenderPass(graphics, scene)); renderGraph.setViewport(viewport); renderGraph.createRenderPass(); - queryThread = std::thread([&]() { - PRenderGraphResources res = renderGraph.getResources(); - Gfx::PPipelineStatisticsQuery cachedQuery = res->requestQuery("CACHED_QUERY"); - Gfx::PPipelineStatisticsQuery depthQuery = res->requestQuery("DEPTH_QUERY"); - Gfx::PPipelineStatisticsQuery baseQuery = res->requestQuery("BASEPASS_QUERY"); - Gfx::PPipelineStatisticsQuery lightCullQuery = res->requestQuery("LIGHTCULL_QUERY"); - Gfx::PPipelineStatisticsQuery visibilityQuery = res->requestQuery("VISIBILITY_QUERY"); - Gfx::PTimestampQuery timeQuery = res->requestTimestampQuery("TIMESTAMP"); - std::ofstream stats("stats.csv"); - stats << "RelTime," - << "CachedIAV,CachedIAP,CachedVS,CachedClipInv,CachedClipPrim,CachedFS,CachedCS,CachedTS,CachedMS," - << "DepthIAV,DepthIAP,DepthVS,DepthClipInv,DepthClipPrim,DepthFS,DepthCS,DepthTS,DepthMS," - << "BaseIAV,BaseIAP,BaseVS,BaseClipInv,BaseClipPrim,BaseFS,BaseCS,BaseTS,BaseMS," - << "LightCullIAV,LightCullIAP,LightCullVS,LightCullClipInv,LightCullClipPrim,LightCullFS,LightCullCS,LightCullTS,LightCullMS," - << "VisibilityIAV,VisibilityIAP,VisibilityVS,VisibilityClipInv,VisibilityClipPrim,VisibilityFS,VisibilityCS,VisibilityTS," - "VisibilityMS," - << "CACHED,MIPGEN,DEPTHCULL,VISIBILITY,LIGHTCULL,BASE" << std::endl; - std::chrono::nanoseconds start = std::chrono::nanoseconds(0); - while (true) { - auto timestamps = timeQuery->getResults(); - auto cachedResults = cachedQuery->getResults(); - auto depthResults = depthQuery->getResults(); - auto baseResults = baseQuery->getResults(); - auto lightCullResults = lightCullQuery->getResults(); - auto visiblityResults = visibilityQuery->getResults(); - if (start.count() == 0) { - start = timestamps[0].time; - } - stats << (timestamps[0].time - start).count() << "," << cachedResults << depthResults << baseResults << lightCullResults - << visiblityResults; - stats << fmt::format("{},{},{},{},{},{}", (timestamps[1].time - timestamps[0].time).count(), - (timestamps[2].time - timestamps[1].time).count(), (timestamps[3].time - timestamps[2].time).count(), - (timestamps[4].time - timestamps[3].time).count(), (timestamps[5].time - timestamps[4].time).count(), - (timestamps[6].time - timestamps[5].time).count()) - << std::endl; - stats.flush(); - } - }); } GameView::~GameView() {} @@ -127,18 +82,6 @@ void GameView::reloadGame() { void GameView::keyCallback(KeyCode code, InputAction action, KeyModifier modifier) { keyboardSystem->keyCallback(code, action, modifier); - if (code == KeyCode::KEY_P && action == InputAction::RELEASE) { - usePositionOnly = !usePositionOnly; - std::cout << "Use Pos only " << usePositionOnly << std::endl; - } - if (code == KeyCode::KEY_O && action == InputAction::RELEASE) { - useDepthCulling = !useDepthCulling; - std::cout << "Use Depth Culling " << useDepthCulling << std::endl; - } - if (code == KeyCode::KEY_L && action == InputAction::RELEASE) { - useLightCulling = !useLightCulling; - std::cout << "Use Light Culling " << useLightCulling << std::endl; - } } void GameView::mouseMoveCallback(double xPos, double yPos) { keyboardSystem->mouseCallback(xPos, yPos); } diff --git a/src/Engine/Window/GameView.h b/src/Engine/Window/GameView.h index 230d36c..7fb949d 100644 --- a/src/Engine/Window/GameView.h +++ b/src/Engine/Window/GameView.h @@ -28,7 +28,6 @@ class GameView : public View { OScene scene; GameInterface gameInterface; RenderGraph renderGraph; - std::thread queryThread; PSystemGraph systemGraph; System::PKeyboardInput keyboardSystem;