diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 67ae71893..437c37c4d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -193,7 +193,7 @@ jobs: update: true - name: Configure CMake working-directory: ${{github.workspace}}/Build - run: ./cmake_linux_mingw.sh ${{matrix.build_type}} g++ -DBUILD_SHARED_LIBS=${{matrix.shared_lib}} + run: ./cmake_linux_mingw.sh ${{matrix.build_type}} -DBUILD_SHARED_LIBS=${{matrix.shared_lib}} - name: Build run: cmake --build Build/MinGW_${{matrix.build_type}} -j $(nproc) - name: Test diff --git a/.gitignore b/.gitignore index 132f40ca4..e75d81b2f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ /snapshot.bin /*.jor /detlog.txt +/Assets/Shaders/*.spv diff --git a/Assets/Shaders/FontPixelShader.frag b/Assets/Shaders/FontPixelShader.frag new file mode 100644 index 000000000..f392fab6c --- /dev/null +++ b/Assets/Shaders/FontPixelShader.frag @@ -0,0 +1,17 @@ +#version 450 + +layout(set = 1, binding = 0) uniform sampler2D texSampler; + +layout(location = 0) in vec2 iTex; +layout(location = 1) in vec4 iColor; + +layout(location = 0) out vec4 oColor; + +void main() +{ + float t = texture(texSampler, iTex).x; + if (t < 0.5) + discard; + + oColor = vec4(iColor.xyz, t); +} diff --git a/Assets/Shaders/FontPixelShader.hlsl b/Assets/Shaders/FontPixelShader.hlsl index 6cdba07be..508753aa9 100644 --- a/Assets/Shaders/FontPixelShader.hlsl +++ b/Assets/Shaders/FontPixelShader.hlsl @@ -18,6 +18,8 @@ PS_OUTPUT main(PS_INPUT In) PS_OUTPUT Output; float t = ShaderTexture.Sample(SampleType, In.Tex).r; + if (t < 0.5) + discard; Output.RGBColor = float4(In.Color.rgb, t); diff --git a/Assets/Shaders/FontVertexShader.vert b/Assets/Shaders/FontVertexShader.vert new file mode 100644 index 000000000..8c5f07fd5 --- /dev/null +++ b/Assets/Shaders/FontVertexShader.vert @@ -0,0 +1,17 @@ +#version 450 + +#include "VertexConstantsVK.h" + +layout(location = 0) in vec3 vPos; +layout(location = 1) in vec2 vTex; +layout(location = 2) in vec4 vCol; + +layout(location = 0) out vec2 oTex; +layout(location = 1) out vec4 oColor; + +void main() +{ + gl_Position = c.Projection * c.View * vec4(vPos, 1.0f); + oTex = vTex; + oColor = vCol; +} diff --git a/Assets/Shaders/LinePixelShader.frag b/Assets/Shaders/LinePixelShader.frag new file mode 100644 index 000000000..50bdb57cf --- /dev/null +++ b/Assets/Shaders/LinePixelShader.frag @@ -0,0 +1,10 @@ +#version 450 + +layout(location = 0) in vec4 iColor; + +layout(location = 0) out vec4 oColor; + +void main() +{ + oColor = iColor; +} \ No newline at end of file diff --git a/Assets/Shaders/LineVertexShader.vert b/Assets/Shaders/LineVertexShader.vert new file mode 100644 index 000000000..cfd4d4245 --- /dev/null +++ b/Assets/Shaders/LineVertexShader.vert @@ -0,0 +1,14 @@ +#version 450 + +#include "VertexConstantsVK.h" + +layout(location = 0) in vec3 iPosition; +layout(location = 1) in vec4 iColor; + +layout(location = 0) out vec4 oColor; + +void main() +{ + gl_Position = c.Projection * c.View * vec4(iPosition, 1.0); + oColor = iColor; +} diff --git a/Assets/Shaders/TriangleDepthPixelShader.frag b/Assets/Shaders/TriangleDepthPixelShader.frag new file mode 100644 index 000000000..d54188f66 --- /dev/null +++ b/Assets/Shaders/TriangleDepthPixelShader.frag @@ -0,0 +1,6 @@ +#version 450 + +// We only write depth, so this shader does nothing +void main() +{ +} diff --git a/Assets/Shaders/TriangleDepthVertexShader.vert b/Assets/Shaders/TriangleDepthVertexShader.vert new file mode 100644 index 000000000..463b446de --- /dev/null +++ b/Assets/Shaders/TriangleDepthVertexShader.vert @@ -0,0 +1,31 @@ +#version 450 + +#include "VertexConstantsVK.h" + +layout(location = 0) in vec3 vPos; +layout(location = 1) in vec3 vNorm; +layout(location = 2) in vec2 vTex; +layout(location = 3) in vec4 vCol; + +layout(location = 4) in mat4 iModel; +layout(location = 8) in mat4 iModelInvTrans; +layout(location = 12) in vec4 iCol; + +void main() +{ + // Check if the alpha = 0 + if (vCol.a * iCol.a == 0.0) + { + // Don't draw the triangle by moving it to an invalid location + gl_Position = vec4(0, 0, 0, 0); + } + else + { + // Transform the position from world space to homogeneous projection space for the light + vec4 pos = vec4(vPos, 1.0f); + pos = iModel * pos; + pos = c.LightView * pos; + pos = c.LightProjection * pos; + gl_Position = pos; + } +} diff --git a/Assets/Shaders/TrianglePixelShader.frag b/Assets/Shaders/TrianglePixelShader.frag new file mode 100644 index 000000000..d57e4a683 --- /dev/null +++ b/Assets/Shaders/TrianglePixelShader.frag @@ -0,0 +1,110 @@ +#version 450 + +layout(binding = 1) uniform PixelShaderConstantBuffer +{ + vec3 CameraPos; + vec3 LightPos; +} c; + +layout(location = 0) in vec3 iNormal; +layout(location = 1) in vec3 iWorldPos; +layout(location = 2) in vec2 iTex; +layout(location = 3) in vec4 iPositionL; +layout(location = 4) in vec4 iColor; + +layout(location = 0) out vec4 oColor; + +layout(set = 1, binding = 0) uniform sampler2DShadow LightDepthSampler; + +void main() +{ + // Constants + float AmbientFactor = 0.3; + vec3 DiffuseColor = vec3(iColor.r, iColor.g, iColor.b); + vec3 SpecularColor = vec3(1, 1, 1); + float SpecularPower = 100.0; + float bias = 1.0e-7; + + // Homogenize position in light space + vec3 position_l = iPositionL.xyz / iPositionL.w; + + // Calculate dot product between direction to light and surface normal and clamp between [0, 1] + vec3 view_dir = normalize(c.CameraPos - iWorldPos); + vec3 world_to_light = c.LightPos - iWorldPos; + vec3 light_dir = normalize(world_to_light); + vec3 normal = normalize(iNormal); + if (dot(view_dir, normal) < 0) // If we're viewing the triangle from the back side, flip the normal to get the correct lighting + normal = -normal; + float normal_dot_light_dir = clamp(dot(normal, light_dir), 0, 1); + + // Calculate texture coordinates in light depth texture + vec2 tex_coord; + tex_coord.x = position_l.x / 2.0 + 0.5; + tex_coord.y = position_l.y / 2.0 + 0.5; + + // Check that the texture coordinate is inside the depth texture, if not we don't know if it is lit or not so we assume lit + float shadow_factor = 1.0; + if (iColor.a > 0 // Alpha = 0 means don't receive shadows + && tex_coord.x == clamp(tex_coord.x, 0, 1) && tex_coord.y == clamp(tex_coord.y, 0, 1)) + { + // Modify shadow bias according to the angle between the normal and the light dir + float modified_bias = bias * tan(acos(normal_dot_light_dir)); + modified_bias = min(modified_bias, 10.0 * bias); + + // Get texture size + float width = 1.0 / 4096; + float height = 1.0 / 4096; + + // Samples to take + uint num_samples = 16; + vec2 offsets[] = { + vec2(-1.5 * width, -1.5 * height), + vec2(-0.5 * width, -1.5 * height), + vec2(0.5 * width, -1.5 * height), + vec2(1.5 * width, -1.5 * height), + + vec2(-1.5 * width, -0.5 * height), + vec2(-0.5 * width, -0.5 * height), + vec2(0.5 * width, -0.5 * height), + vec2(1.5 * width, -0.5 * height), + + vec2(-1.5 * width, 0.5 * height), + vec2(-0.5 * width, 0.5 * height), + vec2(0.5 * width, 0.5 * height), + vec2(1.5 * width, 0.5 * height), + + vec2(-1.5 * width, 1.5 * height), + vec2(-0.5 * width, 1.5 * height), + vec2(0.5 * width, 1.5 * height), + vec2(1.5 * width, 1.5 * height), + }; + + // Calculate depth of this pixel relative to the light + float light_depth = position_l.z + modified_bias; + + // Sample shadow factor + shadow_factor = 0.0; + for (uint i = 0; i < num_samples; ++i) + { + vec3 location_and_reference = vec3(tex_coord + offsets[i], light_depth); + shadow_factor += texture(LightDepthSampler, location_and_reference); + } + shadow_factor /= num_samples; + } + + // Calculate diffuse and specular + float diffuse = normal_dot_light_dir; + float specular = diffuse > 0.0? pow(clamp(-dot(reflect(light_dir, normal), view_dir), 0, 1), SpecularPower) : 0.0; + + // Apply procedural pattern based on the uv coordinates + bvec2 less_half = lessThan(iTex - floor(iTex), vec2(0.5, 0.5)); + float darken_factor = less_half.r ^^ less_half.g? 0.5 : 1.0; + + // Fade out checkerboard pattern when it tiles too often + vec2 dx = dFdx(iTex), dy = dFdy(iTex); + float texel_distance = sqrt(dot(dx, dx) + dot(dy, dy)); + darken_factor = mix(darken_factor, 0.75, clamp(5.0 * texel_distance - 1.5, 0.0, 1.0)); + + // Calculate color + oColor = vec4(clamp((AmbientFactor + diffuse * shadow_factor) * darken_factor * DiffuseColor + SpecularColor * specular * shadow_factor, 0, 1), 1); +} \ No newline at end of file diff --git a/Assets/Shaders/TriangleVertexShader.vert b/Assets/Shaders/TriangleVertexShader.vert new file mode 100644 index 000000000..3d03aa454 --- /dev/null +++ b/Assets/Shaders/TriangleVertexShader.vert @@ -0,0 +1,48 @@ +#version 450 + +#include "VertexConstantsVK.h" + +layout(location = 0) in vec3 vPos; +layout(location = 1) in vec3 vNorm; +layout(location = 2) in vec2 vTex; +layout(location = 3) in vec4 vCol; + +layout(location = 4) in mat4 iModel; +layout(location = 8) in mat4 iModelInvTrans; +layout(location = 12) in vec4 iCol; + +layout(location = 0) out vec3 oNormal; +layout(location = 1) out vec3 oWorldPos; +layout(location = 2) out vec2 oTex; +layout(location = 3) out vec4 oPositionL; +layout(location = 4) out vec4 oColor; + +void main() +{ + // Get world position + vec4 pos = vec4(vPos, 1.0f); + vec4 world_pos = iModel * pos; + + // Transform the position from world space to homogeneous projection space + vec4 proj_pos = c.View * world_pos; + proj_pos = c.Projection * proj_pos; + gl_Position = proj_pos; + + // Transform the position from world space to projection space of the light + vec4 proj_lpos = c.LightView * world_pos; + proj_lpos = c.LightProjection * proj_lpos; + oPositionL = proj_lpos; + + // output normal + vec4 norm = vec4(vNorm, 0.0f); + oNormal = normalize(iModelInvTrans * norm).xyz; + + // output world position of the vertex + oWorldPos = world_pos.xyz; + + // output texture coordinates + oTex = vTex; + + // output color + oColor = vCol * iCol; +} diff --git a/Assets/Shaders/UIPixelShader.frag b/Assets/Shaders/UIPixelShader.frag new file mode 100644 index 000000000..540066493 --- /dev/null +++ b/Assets/Shaders/UIPixelShader.frag @@ -0,0 +1,13 @@ +#version 450 + +layout(set = 1, binding = 0) uniform sampler2D texSampler; + +layout(location = 0) in vec4 iColor; +layout(location = 1) in vec2 iTex; + +layout(location = 0) out vec4 oColor; + +void main() +{ + oColor = iColor * texture(texSampler, iTex); +} diff --git a/Assets/Shaders/UIPixelShaderUntextured.frag b/Assets/Shaders/UIPixelShaderUntextured.frag new file mode 100644 index 000000000..8d161c3df --- /dev/null +++ b/Assets/Shaders/UIPixelShaderUntextured.frag @@ -0,0 +1,10 @@ +#version 450 + +layout(location = 0) in vec4 iColor; + +layout(location = 0) out vec4 oColor; + +void main() +{ + oColor = iColor; +} diff --git a/Assets/Shaders/UIVertexShader.vert b/Assets/Shaders/UIVertexShader.vert new file mode 100644 index 000000000..f0ef39f97 --- /dev/null +++ b/Assets/Shaders/UIVertexShader.vert @@ -0,0 +1,17 @@ +#version 450 + +#include "VertexConstantsVK.h" + +layout(location = 0) in vec3 vPos; +layout(location = 1) in vec2 vTex; +layout(location = 2) in vec4 vCol; + +layout(location = 0) out vec4 oColor; +layout(location = 1) out vec2 oTex; + +void main() +{ + gl_Position = c.Projection * c.View * vec4(vPos, 1.0f); + oTex = vTex; + oColor = vCol; +} diff --git a/Assets/Shaders/VertexConstantsVK.h b/Assets/Shaders/VertexConstantsVK.h new file mode 100644 index 000000000..faffc239f --- /dev/null +++ b/Assets/Shaders/VertexConstantsVK.h @@ -0,0 +1,7 @@ +layout(binding = 0) uniform VertexShaderConstantBuffer +{ + mat4 View; + mat4 Projection; + mat4 LightView; + mat4 LightProjection; +} c; diff --git a/Build/CMakeLists.txt b/Build/CMakeLists.txt index 9e002c909..e69aea7b9 100644 --- a/Build/CMakeLists.txt +++ b/Build/CMakeLists.txt @@ -106,6 +106,9 @@ include(CMakeDependentOption) # Windows Store only supports the DLL version cmake_dependent_option(USE_STATIC_MSVC_RUNTIME_LIBRARY "Use the static MSVC runtime library" ON "MSVC;NOT WINDOWS_STORE" OFF) +# Enable Vulkan instead of DirectX +cmake_dependent_option(JPH_ENABLE_VULKAN "Enable Vulkan" OFF "WIN32" ON) + # Determine which configurations exist if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) # Only do this when we're at the top level, see: https://gitlab.kitware.com/cmake/cmake/-/issues/24181 if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") diff --git a/TestFramework/Application/Application.cpp b/TestFramework/Application/Application.cpp index d811d8bbf..52225f320 100644 --- a/TestFramework/Application/Application.cpp +++ b/TestFramework/Application/Application.cpp @@ -14,6 +14,11 @@ #include #include #include +#ifdef JPH_ENABLE_VULKAN + #include +#else + #include +#endif // Constructor Application::Application() : @@ -52,7 +57,11 @@ Application::Application() : DisableCustomMemoryHook dcmh; // Create renderer - mRenderer = new Renderer; + #ifdef JPH_ENABLE_VULKAN + mRenderer = new RendererVK; + #else + mRenderer = new RendererDX12; + #endif mRenderer->Initialize(); // Create font @@ -226,6 +235,12 @@ void Application::Run() // Start rendering mRenderer->BeginFrame(mWorldCamera, GetWorldScale()); + // Draw from light + static_cast(mDebugRenderer)->DrawShadowPass(); + + // Start drawing normally + mRenderer->EndShadowPass(); + // Draw debug information static_cast(mDebugRenderer)->Draw(); diff --git a/TestFramework/Renderer/CommandQueue.h b/TestFramework/Renderer/DX12/CommandQueueDX12.h similarity index 97% rename from TestFramework/Renderer/CommandQueue.h rename to TestFramework/Renderer/DX12/CommandQueueDX12.h index 9c698ec7b..7a5e69c6e 100644 --- a/TestFramework/Renderer/CommandQueue.h +++ b/TestFramework/Renderer/DX12/CommandQueueDX12.h @@ -4,14 +4,14 @@ #pragma once -#include +#include /// Holds a number of DirectX operations with logic to wait for completion -class CommandQueue +class CommandQueueDX12 { public: /// Destructor - ~CommandQueue() + ~CommandQueueDX12() { WaitUntilFinished(); diff --git a/TestFramework/Renderer/ConstantBuffer.cpp b/TestFramework/Renderer/DX12/ConstantBufferDX12.cpp similarity index 65% rename from TestFramework/Renderer/ConstantBuffer.cpp rename to TestFramework/Renderer/DX12/ConstantBufferDX12.cpp index a660400cd..f018d0f1d 100644 --- a/TestFramework/Renderer/ConstantBuffer.cpp +++ b/TestFramework/Renderer/DX12/ConstantBufferDX12.cpp @@ -4,24 +4,24 @@ #include -#include -#include -#include +#include +#include +#include -ConstantBuffer::ConstantBuffer(Renderer *inRenderer, uint64 inBufferSize) : +ConstantBufferDX12::ConstantBufferDX12(RendererDX12 *inRenderer, uint64 inBufferSize) : mRenderer(inRenderer) { mBuffer = mRenderer->CreateD3DResourceOnUploadHeap(inBufferSize); mBufferSize = inBufferSize; } -ConstantBuffer::~ConstantBuffer() +ConstantBufferDX12::~ConstantBufferDX12() { if (mBuffer != nullptr) mRenderer->RecycleD3DResourceOnUploadHeap(mBuffer.Get(), mBufferSize); } -void *ConstantBuffer::MapInternal() +void *ConstantBufferDX12::MapInternal() { void *mapped_resource; D3D12_RANGE range = { 0, 0 }; // We're not going to read @@ -29,12 +29,12 @@ void *ConstantBuffer::MapInternal() return mapped_resource; } -void ConstantBuffer::Unmap() +void ConstantBufferDX12::Unmap() { mBuffer->Unmap(0, nullptr); } -void ConstantBuffer::Bind(int inSlot) +void ConstantBufferDX12::Bind(int inSlot) { mRenderer->GetCommandList()->SetGraphicsRootConstantBufferView(inSlot, mBuffer->GetGPUVirtualAddress()); } diff --git a/TestFramework/Renderer/ConstantBuffer.h b/TestFramework/Renderer/DX12/ConstantBufferDX12.h similarity index 74% rename from TestFramework/Renderer/ConstantBuffer.h rename to TestFramework/Renderer/DX12/ConstantBufferDX12.h index cb6b1a542..019b3f495 100644 --- a/TestFramework/Renderer/ConstantBuffer.h +++ b/TestFramework/Renderer/DX12/ConstantBufferDX12.h @@ -4,15 +4,15 @@ #pragma once -class Renderer; +class RendererDX12; /// A binary blob that can be used to pass constants to a shader -class ConstantBuffer +class ConstantBufferDX12 { public: /// Constructor - ConstantBuffer(Renderer *inRenderer, uint64 inBufferSize); - ~ConstantBuffer(); + ConstantBufferDX12(RendererDX12 *inRenderer, uint64 inBufferSize); + ~ConstantBufferDX12(); /// Map / unmap buffer (get pointer to data). This will discard all data in the buffer. template T * Map() { return reinterpret_cast(MapInternal()); } @@ -22,11 +22,11 @@ class ConstantBuffer void Bind(int inSlot); private: - friend class Renderer; + friend class RendererDX12; void * MapInternal(); - Renderer * mRenderer; + RendererDX12 * mRenderer; ComPtr mBuffer; uint64 mBufferSize; }; diff --git a/TestFramework/Renderer/DescriptorHeap.h b/TestFramework/Renderer/DX12/DescriptorHeapDX12.h similarity index 99% rename from TestFramework/Renderer/DescriptorHeap.h rename to TestFramework/Renderer/DX12/DescriptorHeapDX12.h index 8706e8eee..9a3afe154 100644 --- a/TestFramework/Renderer/DescriptorHeap.h +++ b/TestFramework/Renderer/DX12/DescriptorHeapDX12.h @@ -5,7 +5,7 @@ #pragma once /// DirectX descriptor heap, used to allocate handles for resources to bind them to shaders -class DescriptorHeap +class DescriptorHeapDX12 { public: /// Initialize the heap diff --git a/TestFramework/Renderer/FatalErrorIfFailed.cpp b/TestFramework/Renderer/DX12/FatalErrorIfFailedDX12.cpp similarity index 90% rename from TestFramework/Renderer/FatalErrorIfFailed.cpp rename to TestFramework/Renderer/DX12/FatalErrorIfFailedDX12.cpp index aef93fea8..8a9d27181 100644 --- a/TestFramework/Renderer/FatalErrorIfFailed.cpp +++ b/TestFramework/Renderer/DX12/FatalErrorIfFailedDX12.cpp @@ -6,7 +6,7 @@ #include -#include +#include #include #include diff --git a/TestFramework/Renderer/FatalErrorIfFailed.h b/TestFramework/Renderer/DX12/FatalErrorIfFailedDX12.h similarity index 79% rename from TestFramework/Renderer/FatalErrorIfFailed.h rename to TestFramework/Renderer/DX12/FatalErrorIfFailedDX12.h index 2d82c2a25..3be2403a6 100644 --- a/TestFramework/Renderer/FatalErrorIfFailed.h +++ b/TestFramework/Renderer/DX12/FatalErrorIfFailedDX12.h @@ -4,6 +4,6 @@ #pragma once -/// Convert DirectX error codes to exceptions +/// Convert DirectX error to readable text and alert void FatalErrorIfFailed(HRESULT inHResult); diff --git a/TestFramework/Renderer/DX12/PipelineStateDX12.cpp b/TestFramework/Renderer/DX12/PipelineStateDX12.cpp new file mode 100644 index 000000000..f0341a826 --- /dev/null +++ b/TestFramework/Renderer/DX12/PipelineStateDX12.cpp @@ -0,0 +1,135 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#include + +#include +#include +#include +#include +#include + +PipelineStateDX12::PipelineStateDX12(RendererDX12 *inRenderer, const VertexShaderDX12 *inVertexShader, const EInputDescription *inInputDescription, uint inInputDescriptionCount, const PixelShaderDX12 *inPixelShader, EDrawPass inDrawPass, EFillMode inFillMode, ETopology inTopology, EDepthTest inDepthTest, EBlendMode inBlendMode, ECullMode inCullMode) : + mRenderer(inRenderer) +{ + D3D12_PRIMITIVE_TOPOLOGY_TYPE topology = inTopology == ETopology::Triangle? D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE : D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE; + + Array input_description; + uint vertex_offset = 0, instance_offset = 0; + for (uint i = 0; i < inInputDescriptionCount; ++i) + switch (inInputDescription[i]) + { + case EInputDescription::Position: + input_description.push_back({ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, vertex_offset, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }); + vertex_offset += 3 * sizeof(float); + break; + + case EInputDescription::Color: + input_description.push_back({ "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, vertex_offset, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }); + vertex_offset += 4 * sizeof(uint8); + break; + + case EInputDescription::Normal: + input_description.push_back({ "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, vertex_offset, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }); + vertex_offset += 3 * sizeof(float); + break; + + case EInputDescription::TexCoord: + input_description.push_back({ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, vertex_offset, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }); + vertex_offset += 2 * sizeof(float); + break; + + case EInputDescription::InstanceColor: + input_description.push_back({ "INSTANCE_COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 1, instance_offset, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 }); + instance_offset += 4 * sizeof(uint8); + break; + + case EInputDescription::InstanceTransform: + { + for (uint j = 0; j < 4; ++j) + { + input_description.push_back({ "INSTANCE_TRANSFORM", j, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, instance_offset, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 }); + instance_offset += 4 * sizeof(float); + } + break; + } + + case EInputDescription::InstanceInvTransform: + { + for (uint j = 0; j < 4; ++j) + { + input_description.push_back({ "INSTANCE_INV_TRANSFORM", j, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, instance_offset, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 }); + instance_offset += 4 * sizeof(float); + } + break; + } + } + + D3D12_GRAPHICS_PIPELINE_STATE_DESC pso_desc = {}; + pso_desc.InputLayout = { input_description.data(), (UINT)input_description.size() }; + pso_desc.pRootSignature = mRenderer->GetRootSignature(); + pso_desc.VS = { inVertexShader->mShader->GetBufferPointer(), inVertexShader->mShader->GetBufferSize() }; + pso_desc.PS = { inPixelShader->mShader->GetBufferPointer(), inPixelShader->mShader->GetBufferSize() }; + + pso_desc.RasterizerState.FillMode = inFillMode == EFillMode::Solid? D3D12_FILL_MODE_SOLID : D3D12_FILL_MODE_WIREFRAME; + pso_desc.RasterizerState.CullMode = inCullMode == ECullMode::Backface? D3D12_CULL_MODE_FRONT : D3D12_CULL_MODE_BACK; // DX uses left handed system so we reverse the options + pso_desc.RasterizerState.FrontCounterClockwise = FALSE; + pso_desc.RasterizerState.DepthBias = D3D12_DEFAULT_DEPTH_BIAS; + pso_desc.RasterizerState.DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP; + pso_desc.RasterizerState.SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS; + pso_desc.RasterizerState.DepthClipEnable = TRUE; + pso_desc.RasterizerState.MultisampleEnable = FALSE; + pso_desc.RasterizerState.AntialiasedLineEnable = FALSE; + pso_desc.RasterizerState.ForcedSampleCount = 0; + pso_desc.RasterizerState.ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF; + + pso_desc.BlendState.AlphaToCoverageEnable = FALSE; + pso_desc.BlendState.IndependentBlendEnable = FALSE; + + D3D12_RENDER_TARGET_BLEND_DESC &blend_desc = pso_desc.BlendState.RenderTarget[0]; + blend_desc.LogicOpEnable = FALSE; + blend_desc.LogicOp = D3D12_LOGIC_OP_NOOP; + blend_desc.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL; + switch (inBlendMode) + { + case EBlendMode::Write: + blend_desc.BlendEnable = FALSE; + break; + + case EBlendMode::AlphaBlend: + blend_desc.BlendEnable = TRUE; + blend_desc.SrcBlend = D3D12_BLEND_SRC_ALPHA; + blend_desc.DestBlend = D3D12_BLEND_INV_SRC_ALPHA; + blend_desc.BlendOp = D3D12_BLEND_OP_ADD; + blend_desc.SrcBlendAlpha = D3D12_BLEND_ZERO; + blend_desc.DestBlendAlpha = D3D12_BLEND_ZERO; + blend_desc.BlendOpAlpha = D3D12_BLEND_OP_ADD; + break; + } + + pso_desc.DepthStencilState.DepthEnable = inDepthTest == EDepthTest::On? TRUE : FALSE; + pso_desc.DepthStencilState.DepthWriteMask = inDepthTest == EDepthTest::On? D3D12_DEPTH_WRITE_MASK_ALL : D3D12_DEPTH_WRITE_MASK_ZERO; + pso_desc.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_GREATER; + pso_desc.DepthStencilState.StencilEnable = FALSE; + + pso_desc.SampleMask = UINT_MAX; + pso_desc.PrimitiveTopologyType = topology; + pso_desc.NumRenderTargets = 1; + pso_desc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM; + pso_desc.DSVFormat = DXGI_FORMAT_D32_FLOAT; + pso_desc.SampleDesc.Count = 1; + + FatalErrorIfFailed(mRenderer->GetDevice()->CreateGraphicsPipelineState(&pso_desc, IID_PPV_ARGS(&mPSO))); +} + +PipelineStateDX12::~PipelineStateDX12() +{ + if (mPSO != nullptr) + mRenderer->RecycleD3DObject(mPSO.Get()); +} + +void PipelineStateDX12::Activate() +{ + mRenderer->GetCommandList()->SetPipelineState(mPSO.Get()); +} diff --git a/TestFramework/Renderer/DX12/PipelineStateDX12.h b/TestFramework/Renderer/DX12/PipelineStateDX12.h new file mode 100644 index 000000000..df3ddf321 --- /dev/null +++ b/TestFramework/Renderer/DX12/PipelineStateDX12.h @@ -0,0 +1,27 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +class RendererDX12; +class VertexShaderDX12; +class PixelShaderDX12; + +/// DirectX 12 pipeline state object +class PipelineStateDX12 : public PipelineState +{ +public: + /// Constructor + PipelineStateDX12(RendererDX12 *inRenderer, const VertexShaderDX12 *inVertexShader, const EInputDescription *inInputDescription, uint inInputDescriptionCount, const PixelShaderDX12 *inPixelShader, EDrawPass inDrawPass, EFillMode inFillMode, ETopology inTopology, EDepthTest inDepthTest, EBlendMode inBlendMode, ECullMode inCullMode); + virtual ~PipelineStateDX12() override; + + /// Make this pipeline state active (any primitives rendered after this will use this state) + virtual void Activate() override; + +private: + RendererDX12 * mRenderer; + ComPtr mPSO; +}; diff --git a/TestFramework/Renderer/DX12/PixelShaderDX12.h b/TestFramework/Renderer/DX12/PixelShaderDX12.h new file mode 100644 index 000000000..e1a59e41c --- /dev/null +++ b/TestFramework/Renderer/DX12/PixelShaderDX12.h @@ -0,0 +1,17 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +/// Pixel shader handle for DirectX +class PixelShaderDX12 : public PixelShader +{ +public: + /// Constructor + PixelShaderDX12(ComPtr inShader) : mShader(inShader) { } + + ComPtr mShader; ///< The compiled shader +}; diff --git a/TestFramework/Renderer/RenderInstances.cpp b/TestFramework/Renderer/DX12/RenderInstancesDX12.cpp similarity index 55% rename from TestFramework/Renderer/RenderInstances.cpp rename to TestFramework/Renderer/DX12/RenderInstancesDX12.cpp index 758fb1cf8..ad700bb98 100644 --- a/TestFramework/Renderer/RenderInstances.cpp +++ b/TestFramework/Renderer/DX12/RenderInstancesDX12.cpp @@ -1,14 +1,14 @@ // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) -// SPDX-FileCopyrightText: 2021 Jorrit Rouwe +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe // SPDX-License-Identifier: MIT #include -#include -#include -#include +#include +#include +#include -void RenderInstances::Clear() +void RenderInstancesDX12::Clear() { if (mInstanceBuffer != nullptr) mRenderer->RecycleD3DResourceOnUploadHeap(mInstanceBuffer.Get(), mInstanceBufferSize); @@ -18,7 +18,7 @@ void RenderInstances::Clear() mInstanceSize = 0; } -void RenderInstances::CreateBuffer(int inNumInstances, int inInstanceSize) +void RenderInstancesDX12::CreateBuffer(int inNumInstances, int inInstanceSize) { if (mInstanceBuffer == nullptr || mInstanceBufferSize < inNumInstances * inInstanceSize) { @@ -37,7 +37,7 @@ void RenderInstances::CreateBuffer(int inNumInstances, int inInstanceSize) mInstanceSize = inInstanceSize; } -void *RenderInstances::Lock() +void *RenderInstancesDX12::Lock() { uint32 *mapped_resource; D3D12_RANGE range = { 0, 0 }; @@ -45,27 +45,29 @@ void *RenderInstances::Lock() return mapped_resource; } -void RenderInstances::Unlock() +void RenderInstancesDX12::Unlock() { mInstanceBuffer->Unmap(0, nullptr); } -void RenderInstances::Draw(RenderPrimitive *inPrimitive, int inStartInstance, int inNumInstances) const +void RenderInstancesDX12::Draw(RenderPrimitive *inPrimitive, int inStartInstance, int inNumInstances) const { if (inNumInstances <= 0) return; + RenderPrimitiveDX12 *primitive = static_cast(inPrimitive); + ID3D12GraphicsCommandList *command_list = mRenderer->GetCommandList(); // Set topology - command_list->IASetPrimitiveTopology(inPrimitive->mType); + command_list->IASetPrimitiveTopology(primitive->mType == PipelineState::ETopology::Triangle? D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST : D3D_PRIMITIVE_TOPOLOGY_LINELIST); D3D12_VERTEX_BUFFER_VIEW vb_view[2]; // Vertex buffer - vb_view[0].BufferLocation = inPrimitive->mVtxBuffer->GetGPUVirtualAddress(); - vb_view[0].StrideInBytes = inPrimitive->mVtxSize; - vb_view[0].SizeInBytes = inPrimitive->mNumVtxToDraw * inPrimitive->mVtxSize; + vb_view[0].BufferLocation = primitive->mVtxBuffer->GetGPUVirtualAddress(); + vb_view[0].StrideInBytes = primitive->mVtxSize; + vb_view[0].SizeInBytes = primitive->mNumVtxToDraw * primitive->mVtxSize; // Instances buffer vb_view[1].BufferLocation = mInstanceBuffer->GetGPUVirtualAddress(); @@ -74,21 +76,21 @@ void RenderInstances::Draw(RenderPrimitive *inPrimitive, int inStartInstance, in command_list->IASetVertexBuffers(0, 2, &vb_view[0]); - if (inPrimitive->mIdxBuffer == nullptr) + if (primitive->mIdxBuffer == nullptr) { // Draw instanced primitive - command_list->DrawInstanced(inPrimitive->mNumVtxToDraw, inNumInstances, 0, inStartInstance); + command_list->DrawInstanced(primitive->mNumVtxToDraw, inNumInstances, 0, inStartInstance); } else { // Set index buffer D3D12_INDEX_BUFFER_VIEW ib_view; - ib_view.BufferLocation = inPrimitive->mIdxBuffer->GetGPUVirtualAddress(); - ib_view.SizeInBytes = inPrimitive->mNumIdxToDraw * sizeof(uint32); + ib_view.BufferLocation = primitive->mIdxBuffer->GetGPUVirtualAddress(); + ib_view.SizeInBytes = primitive->mNumIdxToDraw * sizeof(uint32); ib_view.Format = DXGI_FORMAT_R32_UINT; command_list->IASetIndexBuffer(&ib_view); // Draw instanced primitive - command_list->DrawIndexedInstanced(inPrimitive->mNumIdxToDraw, inNumInstances, 0, 0, inStartInstance); + command_list->DrawIndexedInstanced(primitive->mNumIdxToDraw, inNumInstances, 0, 0, inStartInstance); } } diff --git a/TestFramework/Renderer/DX12/RenderInstancesDX12.h b/TestFramework/Renderer/DX12/RenderInstancesDX12.h new file mode 100644 index 000000000..81af83498 --- /dev/null +++ b/TestFramework/Renderer/DX12/RenderInstancesDX12.h @@ -0,0 +1,37 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include + +class RenderPrimitive; + +/// DirectX 12 implementation of a render instances object +class RenderInstancesDX12 : public RenderInstances +{ +public: + /// Constructor + RenderInstancesDX12(RendererDX12 *inRenderer) : mRenderer(inRenderer) { } + virtual ~RenderInstancesDX12() override { Clear(); } + + /// Erase all instance data + virtual void Clear() override; + + /// Instance buffer management functions + virtual void CreateBuffer(int inNumInstances, int inInstanceSize) override; + virtual void * Lock() override; + virtual void Unlock() override; + + /// Draw the instances when context has been set by Renderer::BindShader + virtual void Draw(RenderPrimitive *inPrimitive, int inStartInstance, int inNumInstances) const override; + +private: + RendererDX12 * mRenderer; + + ComPtr mInstanceBuffer; + int mInstanceBufferSize = 0; + int mInstanceSize = 0; +}; diff --git a/TestFramework/Renderer/DX12/RenderPrimitiveDX12.cpp b/TestFramework/Renderer/DX12/RenderPrimitiveDX12.cpp new file mode 100644 index 000000000..a96d7d0fb --- /dev/null +++ b/TestFramework/Renderer/DX12/RenderPrimitiveDX12.cpp @@ -0,0 +1,148 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#include + +#include +#include + +void RenderPrimitiveDX12::ReleaseVertexBuffer() +{ + if (mVtxBuffer != nullptr) + { + if (mVtxBufferInUploadHeap) + mRenderer->RecycleD3DResourceOnUploadHeap(mVtxBuffer.Get(), mNumVtx * mVtxSize); + else + mRenderer->RecycleD3DObject(mVtxBuffer.Get()); + mVtxBuffer = nullptr; + } + + mVtxBufferInUploadHeap = false; + RenderPrimitive::ReleaseVertexBuffer(); +} + +void RenderPrimitiveDX12::ReleaseIndexBuffer() +{ + if (mIdxBuffer != nullptr) + { + if (mIdxBufferInUploadHeap) + mRenderer->RecycleD3DResourceOnUploadHeap(mIdxBuffer.Get(), mNumIdx * sizeof(uint32)); + else + mRenderer->RecycleD3DObject(mIdxBuffer.Get()); + mIdxBuffer = nullptr; + } + + mIdxBufferInUploadHeap = false; + RenderPrimitive::ReleaseIndexBuffer(); +} + +void RenderPrimitiveDX12::CreateVertexBuffer(int inNumVtx, int inVtxSize, const void *inData) +{ + RenderPrimitive::CreateVertexBuffer(inNumVtx, inVtxSize, inData); + + uint64 size = uint64(inNumVtx) * inVtxSize; + + if (inData != nullptr) + { + // Data provided, assume the buffer is static so allocate it on the GPU + mVtxBuffer = mRenderer->CreateD3DResourceOnDefaultHeap(inData, size); + mVtxBufferInUploadHeap = false; + } + else + { + // No data provided, create a buffer that will be uploaded to the GPU every time it is used + mVtxBuffer = mRenderer->CreateD3DResourceOnUploadHeap(size); + mVtxBufferInUploadHeap = true; + } + + JPH_IF_DEBUG(mVtxBuffer->SetName(L"Vertex Buffer");) +} + +void *RenderPrimitiveDX12::LockVertexBuffer() +{ + void *mapped_resource; + D3D12_RANGE range = { 0, 0 }; + FatalErrorIfFailed(mVtxBuffer->Map(0, &range, &mapped_resource)); + return mapped_resource; +} + +void RenderPrimitiveDX12::UnlockVertexBuffer() +{ + mVtxBuffer->Unmap(0, nullptr); +} + +void RenderPrimitiveDX12::CreateIndexBuffer(int inNumIdx, const uint32 *inData) +{ + RenderPrimitive::CreateIndexBuffer(inNumIdx, inData); + + uint64 size = uint64(inNumIdx) * sizeof(uint32); + + if (inData != nullptr) + { + // Data provided, assume the buffer is static so allocate it on the GPU + mIdxBuffer = mRenderer->CreateD3DResourceOnDefaultHeap(inData, size); + mIdxBufferInUploadHeap = false; + } + else + { + // No data provided, create a buffer that will be uploaded to the GPU every time it is used + mIdxBuffer = mRenderer->CreateD3DResourceOnUploadHeap(size); + mIdxBufferInUploadHeap = true; + } + + JPH_IF_DEBUG(mIdxBuffer->SetName(L"Index Buffer");) +} + +uint32 *RenderPrimitiveDX12::LockIndexBuffer() +{ + uint32 *mapped_resource; + D3D12_RANGE range = { 0, 0 }; + FatalErrorIfFailed(mIdxBuffer->Map(0, &range, (void **)&mapped_resource)); + return mapped_resource; +} + +void RenderPrimitiveDX12::UnlockIndexBuffer() +{ + mIdxBuffer->Unmap(0, nullptr); +} + +void RenderPrimitiveDX12::Draw() const +{ + ID3D12GraphicsCommandList *command_list = mRenderer->GetCommandList(); + + // Set topology + command_list->IASetPrimitiveTopology(mType == PipelineState::ETopology::Triangle? D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST : D3D_PRIMITIVE_TOPOLOGY_LINELIST); + + if (mIdxBuffer == nullptr) + { + // Set vertex buffer + D3D12_VERTEX_BUFFER_VIEW vb_view; + vb_view.BufferLocation = mVtxBuffer->GetGPUVirtualAddress(); + vb_view.StrideInBytes = mVtxSize; + vb_view.SizeInBytes = mNumVtxToDraw * mVtxSize; + command_list->IASetVertexBuffers(0, 1, &vb_view); + + // Draw the non indexed primitive + command_list->DrawInstanced(mNumVtxToDraw, 1, 0, 0); + } + else + { + // Set vertex buffer + D3D12_VERTEX_BUFFER_VIEW vb_view; + vb_view.BufferLocation = mVtxBuffer->GetGPUVirtualAddress(); + vb_view.StrideInBytes = mVtxSize; + vb_view.SizeInBytes = mNumVtx * mVtxSize; + command_list->IASetVertexBuffers(0, 1, &vb_view); + + // Set index buffer + D3D12_INDEX_BUFFER_VIEW ib_view; + ib_view.BufferLocation = mIdxBuffer->GetGPUVirtualAddress(); + ib_view.SizeInBytes = mNumIdxToDraw * sizeof(uint32); + ib_view.Format = DXGI_FORMAT_R32_UINT; + command_list->IASetIndexBuffer(&ib_view); + + // Draw indexed primitive + command_list->DrawIndexedInstanced(mNumIdxToDraw, 1, 0, 0, 0); + } +} diff --git a/TestFramework/Renderer/DX12/RenderPrimitiveDX12.h b/TestFramework/Renderer/DX12/RenderPrimitiveDX12.h new file mode 100644 index 000000000..3514996cf --- /dev/null +++ b/TestFramework/Renderer/DX12/RenderPrimitiveDX12.h @@ -0,0 +1,45 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include + +/// DirectX 12 implementation of a render primitive +class RenderPrimitiveDX12 : public RenderPrimitive +{ +public: + /// Constructor + RenderPrimitiveDX12(RendererDX12 *inRenderer, PipelineState::ETopology inType) : mRenderer(inRenderer), mType(inType) { } + virtual ~RenderPrimitiveDX12() override { Clear(); } + + /// Vertex buffer management functions + virtual void CreateVertexBuffer(int inNumVtx, int inVtxSize, const void *inData = nullptr) override; + virtual void ReleaseVertexBuffer() override; + virtual void * LockVertexBuffer() override; + virtual void UnlockVertexBuffer() override; + + /// Index buffer management functions + virtual void CreateIndexBuffer(int inNumIdx, const uint32 *inData = nullptr) override; + virtual void ReleaseIndexBuffer() override; + virtual uint32 * LockIndexBuffer() override; + virtual void UnlockIndexBuffer() override; + + /// Draw the primitive + virtual void Draw() const override; + +private: + friend class RenderInstancesDX12; + + RendererDX12 * mRenderer; + + PipelineState::ETopology mType; + + ComPtr mVtxBuffer; + bool mVtxBufferInUploadHeap = false; + + ComPtr mIdxBuffer; + bool mIdxBufferInUploadHeap = false; +}; diff --git a/TestFramework/Renderer/DX12/RendererDX12.cpp b/TestFramework/Renderer/DX12/RendererDX12.cpp new file mode 100644 index 000000000..92c49251c --- /dev/null +++ b/TestFramework/Renderer/DX12/RendererDX12.cpp @@ -0,0 +1,708 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#ifdef JPH_DEBUG + #include +#endif + +RendererDX12::~RendererDX12() +{ + // Ensure that the GPU is no longer referencing resources that are about to be cleaned up by the destructor. + WaitForGpu(); + + // Don't add more stuff to the delay reference list + mIsExiting = true; + + CloseHandle(mFenceEvent); +} + +void RendererDX12::WaitForGpu() +{ + // Schedule a Signal command in the queue + UINT64 current_fence_value = mFenceValues[mFrameIndex]; + FatalErrorIfFailed(mCommandQueue->Signal(mFence.Get(), current_fence_value)); + + // Wait until the fence has been processed + FatalErrorIfFailed(mFence->SetEventOnCompletion(current_fence_value, mFenceEvent)); + WaitForSingleObjectEx(mFenceEvent, INFINITE, FALSE); + + // Increment the fence value for all frames + for (uint n = 0; n < cFrameCount; ++n) + mFenceValues[n] = current_fence_value + 1; + + // Release all used resources + for (Array> &list : mDelayReleased) + list.clear(); + + // Anything that's not used yet can be removed, delayed objects are now available + mResourceCache.clear(); + mDelayCached[mFrameIndex].swap(mResourceCache); +} + +void RendererDX12::CreateRenderTargets() +{ + // Create render targets and views + for (uint n = 0; n < cFrameCount; ++n) + { + mRenderTargetViews[n] = mRTVHeap.Allocate(); + + FatalErrorIfFailed(mSwapChain->GetBuffer(n, IID_PPV_ARGS(&mRenderTargets[n]))); + mDevice->CreateRenderTargetView(mRenderTargets[n].Get(), nullptr, mRenderTargetViews[n]); + } +} + +void RendererDX12::CreateDepthBuffer() +{ + // Free any previous depth stencil view + if (mDepthStencilView.ptr != 0) + mDSVHeap.Free(mDepthStencilView); + + // Free any previous depth stencil buffer + mDepthStencilBuffer.Reset(); + + // Allocate depth stencil buffer + D3D12_CLEAR_VALUE clear_value = {}; + clear_value.Format = DXGI_FORMAT_D32_FLOAT; + clear_value.DepthStencil.Depth = 0.0f; + clear_value.DepthStencil.Stencil = 0; + + D3D12_HEAP_PROPERTIES heap_properties = {}; + heap_properties.Type = D3D12_HEAP_TYPE_DEFAULT; + heap_properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + heap_properties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + heap_properties.CreationNodeMask = 1; + heap_properties.VisibleNodeMask = 1; + + D3D12_RESOURCE_DESC depth_stencil_desc = {}; + depth_stencil_desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; + depth_stencil_desc.Alignment = 0; + depth_stencil_desc.Width = mWindowWidth; + depth_stencil_desc.Height = mWindowHeight; + depth_stencil_desc.DepthOrArraySize = 1; + depth_stencil_desc.MipLevels = 1; + depth_stencil_desc.Format = DXGI_FORMAT_D32_FLOAT; + depth_stencil_desc.SampleDesc.Count = 1; + depth_stencil_desc.SampleDesc.Quality = 0; + depth_stencil_desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; + depth_stencil_desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; + + FatalErrorIfFailed(mDevice->CreateCommittedResource(&heap_properties, D3D12_HEAP_FLAG_NONE, &depth_stencil_desc, D3D12_RESOURCE_STATE_DEPTH_WRITE, &clear_value, IID_PPV_ARGS(&mDepthStencilBuffer))); + + // Allocate depth stencil view + D3D12_DEPTH_STENCIL_VIEW_DESC depth_stencil_view_desc = {}; + depth_stencil_view_desc.Format = DXGI_FORMAT_D32_FLOAT; + depth_stencil_view_desc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D; + depth_stencil_view_desc.Flags = D3D12_DSV_FLAG_NONE; + + mDepthStencilView = mDSVHeap.Allocate(); + mDevice->CreateDepthStencilView(mDepthStencilBuffer.Get(), &depth_stencil_view_desc, mDepthStencilView); +} + +void RendererDX12::Initialize() +{ + Renderer::Initialize(); + +#if defined(JPH_DEBUG) + // Enable the D3D12 debug layer + ComPtr debug_controller; + if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debug_controller)))) + debug_controller->EnableDebugLayer(); +#endif + + // Create DXGI factory + FatalErrorIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(&mDXGIFactory))); + + // Find adapter + ComPtr adapter; + + HRESULT result = E_FAIL; + + // First check if we have the Windows 1803 IDXGIFactory6 interface + ComPtr factory6; + if (SUCCEEDED(mDXGIFactory->QueryInterface(IID_PPV_ARGS(&factory6)))) + { + for (UINT index = 0; DXGI_ERROR_NOT_FOUND != factory6->EnumAdapterByGpuPreference(index, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, IID_PPV_ARGS(&adapter)); ++index) + { + DXGI_ADAPTER_DESC1 desc; + adapter->GetDesc1(&desc); + + // We don't want software renderers + if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) + continue; + + // Check to see whether the adapter supports Direct3D 12 + result = D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&mDevice)); + if (SUCCEEDED(result)) + break; + } + } + else + { + // Fall back to the older method that may not get the fastest GPU + for (UINT index = 0; DXGI_ERROR_NOT_FOUND != mDXGIFactory->EnumAdapters1(index, &adapter); ++index) + { + DXGI_ADAPTER_DESC1 desc; + adapter->GetDesc1(&desc); + + // We don't want software renderers + if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) + continue; + + // Check to see whether the adapter supports Direct3D 12 + result = D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&mDevice)); + if (SUCCEEDED(result)) + break; + } + } + + // Check if we managed to obtain a device + FatalErrorIfFailed(result); + +#ifdef JPH_DEBUG + // Enable breaking on errors + ComPtr info_queue; + if (SUCCEEDED(mDevice.As(&info_queue))) + { + info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, TRUE); + info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, TRUE); + info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, TRUE); + + // Disable an error that triggers on Windows 11 with a hybrid graphic system + // See: https://stackoverflow.com/questions/69805245/directx-12-application-is-crashing-in-windows-11 + D3D12_MESSAGE_ID hide[] = + { + D3D12_MESSAGE_ID_RESOURCE_BARRIER_MISMATCHING_COMMAND_LIST_TYPE, + }; + D3D12_INFO_QUEUE_FILTER filter = { }; + filter.DenyList.NumIDs = static_cast( std::size( hide ) ); + filter.DenyList.pIDList = hide; + info_queue->AddStorageFilterEntries( &filter ); + } +#endif // JPH_DEBUG + + // Disable full screen transitions + FatalErrorIfFailed(mDXGIFactory->MakeWindowAssociation(mhWnd, DXGI_MWA_NO_ALT_ENTER)); + + // Create heaps + mRTVHeap.Init(mDevice.Get(), D3D12_DESCRIPTOR_HEAP_TYPE_RTV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 2); + mDSVHeap.Init(mDevice.Get(), D3D12_DESCRIPTOR_HEAP_TYPE_DSV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 4); + mSRVHeap.Init(mDevice.Get(), D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE, 128); + + // Create a command queue + D3D12_COMMAND_QUEUE_DESC queue_desc = {}; + queue_desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; + queue_desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; + FatalErrorIfFailed(mDevice->CreateCommandQueue(&queue_desc, IID_PPV_ARGS(&mCommandQueue))); + + // Create a command allocator for each frame + for (uint n = 0; n < cFrameCount; n++) + FatalErrorIfFailed(mDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&mCommandAllocators[n]))); + + // Describe and create the swap chain + DXGI_SWAP_CHAIN_DESC swap_chain_desc = {}; + swap_chain_desc.BufferCount = cFrameCount; + swap_chain_desc.BufferDesc.Width = mWindowWidth; + swap_chain_desc.BufferDesc.Height = mWindowHeight; + swap_chain_desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; + swap_chain_desc.OutputWindow = mhWnd; + swap_chain_desc.SampleDesc.Count = 1; + swap_chain_desc.Windowed = TRUE; + + ComPtr swap_chain; + FatalErrorIfFailed(mDXGIFactory->CreateSwapChain(mCommandQueue.Get(), &swap_chain_desc, &swap_chain)); + FatalErrorIfFailed(swap_chain.As(&mSwapChain)); + mFrameIndex = mSwapChain->GetCurrentBackBufferIndex(); + + CreateRenderTargets(); + + CreateDepthBuffer(); + + // Create a root signature suitable for all our shaders + D3D12_ROOT_PARAMETER params[3] = {}; + + // Mapping a constant buffer to slot 0 for the vertex shader + params[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV; + params[0].Descriptor.ShaderRegister = 0; + params[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX; + + // Mapping a constant buffer to slot 1 in the pixel shader + params[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV; + params[1].Descriptor.ShaderRegister = 1; + params[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; + + // Mapping a texture to slot 2 in the pixel shader + D3D12_DESCRIPTOR_RANGE range = {}; + range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; + range.BaseShaderRegister = 2; + range.NumDescriptors = 1; + params[2].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + params[2].DescriptorTable.NumDescriptorRanges = 1; + params[2].DescriptorTable.pDescriptorRanges = ⦥ + params[2].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; + + D3D12_STATIC_SAMPLER_DESC samplers[3] = {}; + + // Sampler 0: Non-wrapping linear filtering + samplers[0].Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR; + samplers[0].AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; + samplers[0].AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; + samplers[0].AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; + samplers[0].MipLODBias = 0.0f; + samplers[0].MaxAnisotropy = 1; + samplers[0].ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS; + samplers[0].BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK; + samplers[0].MinLOD = 0.0f; + samplers[0].MaxLOD = D3D12_FLOAT32_MAX; + samplers[0].ShaderRegister = 0; + samplers[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; + + // Sampler 1: Wrapping and linear filtering + samplers[1] = samplers[0]; + samplers[1].AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP; + samplers[1].AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP; + samplers[1].AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP; + samplers[1].ShaderRegister = 1; + + // Sampler 2: Point filtering, using SampleCmp mode to compare if sampled value >= reference value (for shadows) + samplers[2] = samplers[0]; + samplers[2].Filter = D3D12_FILTER_COMPARISON_MIN_MAG_LINEAR_MIP_POINT; + samplers[2].ComparisonFunc = D3D12_COMPARISON_FUNC_GREATER_EQUAL; + samplers[2].ShaderRegister = 2; + + D3D12_ROOT_SIGNATURE_DESC root_signature_desc = {}; + root_signature_desc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT; + root_signature_desc.NumParameters = ARRAYSIZE(params); + root_signature_desc.pParameters = params; + root_signature_desc.NumStaticSamplers = ARRAYSIZE(samplers); + root_signature_desc.pStaticSamplers = samplers; + + ComPtr signature; + ComPtr error; + FatalErrorIfFailed(D3D12SerializeRootSignature(&root_signature_desc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error)); + FatalErrorIfFailed(mDevice->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&mRootSignature))); + + // Create the command list + FatalErrorIfFailed(mDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, mCommandAllocators[mFrameIndex].Get(), nullptr, IID_PPV_ARGS(&mCommandList))); + + // Command lists are created in the recording state, but there is nothing to record yet. The main loop expects it to be closed, so close it now + FatalErrorIfFailed(mCommandList->Close()); + + // Create synchronization object + FatalErrorIfFailed(mDevice->CreateFence(mFenceValues[mFrameIndex], D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence))); + + // Increment fence value so we don't skip waiting the first time a command list is executed + mFenceValues[mFrameIndex]++; + + // Create an event handle to use for frame synchronization + mFenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); + if (mFenceEvent == nullptr) + FatalErrorIfFailed(HRESULT_FROM_WIN32(GetLastError())); + + // Initialize the queue used to upload resources to the GPU + mUploadQueue.Initialize(mDevice.Get()); + + // Create constant buffer. One per frame to avoid overwriting the constant buffer while the GPU is still using it. + for (uint n = 0; n < cFrameCount; ++n) + { + mVertexShaderConstantBufferProjection[n] = CreateConstantBuffer(sizeof(VertexShaderConstantBuffer)); + mVertexShaderConstantBufferOrtho[n] = CreateConstantBuffer(sizeof(VertexShaderConstantBuffer)); + mPixelShaderConstantBuffer[n] = CreateConstantBuffer(sizeof(PixelShaderConstantBuffer)); + } + + // Create depth only texture (no color buffer, as seen from light) + mShadowMap = new TextureDX12(this, cShadowMapSize, cShadowMapSize); +} + +void RendererDX12::OnWindowResize() +{ + Renderer::OnWindowResize(); + + // Wait for the previous frame to be rendered + WaitForGpu(); + + // Free the render targets and views to allow resizing the swap chain + for (uint n = 0; n < cFrameCount; ++n) + { + mRTVHeap.Free(mRenderTargetViews[n]); + mRenderTargets[n].Reset(); + } + + // Resize the swap chain buffers + FatalErrorIfFailed(mSwapChain->ResizeBuffers(cFrameCount, mWindowWidth, mWindowHeight, DXGI_FORMAT_R8G8B8A8_UNORM, 0)); + + // Back buffer index may have changed after the resize (it always seems to go to 0 again) + mFrameIndex = mSwapChain->GetCurrentBackBufferIndex(); + + // Since we may have switched frame index and we know everything is done, we need to update the fence value for our other frame as completed + for (uint n = 0; n < cFrameCount; ++n) + if (mFrameIndex != n) + mFenceValues[n] = mFence->GetCompletedValue(); + + // Recreate render targets + CreateRenderTargets(); + + // Recreate depth buffer + CreateDepthBuffer(); +} + +void RendererDX12::BeginFrame(const CameraState &inCamera, float inWorldScale) +{ + JPH_PROFILE_FUNCTION(); + + Renderer::BeginFrame(inCamera, inWorldScale); + + // Reset command allocator + FatalErrorIfFailed(mCommandAllocators[mFrameIndex]->Reset()); + + // Reset command list + FatalErrorIfFailed(mCommandList->Reset(mCommandAllocators[mFrameIndex].Get(), nullptr)); + + // Set root signature + mCommandList->SetGraphicsRootSignature(mRootSignature.Get()); + + // Set SRV heap + ID3D12DescriptorHeap *heaps[] = { mSRVHeap.Get() }; + mCommandList->SetDescriptorHeaps(_countof(heaps), heaps); + + // Indicate that the back buffer will be used as a render target. + D3D12_RESOURCE_BARRIER barrier; + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; + barrier.Transition.pResource = mRenderTargets[mFrameIndex].Get(); + barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET; + barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + mCommandList->ResourceBarrier(1, &barrier); + + // Clear the back buffer. + const float blue[] = { 0.098f, 0.098f, 0.439f, 1.000f }; + mCommandList->ClearRenderTargetView(mRenderTargetViews[mFrameIndex], blue, 0, nullptr); + mCommandList->ClearDepthStencilView(mDepthStencilView, D3D12_CLEAR_FLAG_DEPTH, 0.0f, 0, 0, nullptr); + + // Set constants for vertex shader in projection mode + VertexShaderConstantBuffer *vs = mVertexShaderConstantBufferProjection[mFrameIndex]->Map(); + *vs = mVSBuffer; + mVertexShaderConstantBufferProjection[mFrameIndex]->Unmap(); + + // Set constants for vertex shader in ortho mode + vs = mVertexShaderConstantBufferOrtho[mFrameIndex]->Map(); + *vs = mVSBufferOrtho; + mVertexShaderConstantBufferOrtho[mFrameIndex]->Unmap(); + + // Switch to 3d projection mode + SetProjectionMode(); + + // Set constants for pixel shader + PixelShaderConstantBuffer *ps = mPixelShaderConstantBuffer[mFrameIndex]->Map(); + *ps = mPSBuffer; + mPixelShaderConstantBuffer[mFrameIndex]->Unmap(); + + // Set the pixel shader constant buffer data. + mPixelShaderConstantBuffer[mFrameIndex]->Bind(1); + + // Start drawing the shadow pass + mShadowMap->SetAsRenderTarget(true); +} + +void RendererDX12::EndShadowPass() +{ + JPH_PROFILE_FUNCTION(); + + // Finish drawing the shadow pass + mShadowMap->SetAsRenderTarget(false); + + // Set the main back buffer as render target + mCommandList->OMSetRenderTargets(1, &mRenderTargetViews[mFrameIndex], FALSE, &mDepthStencilView); + + // Set viewport + D3D12_VIEWPORT viewport = { 0.0f, 0.0f, static_cast(mWindowWidth), static_cast(mWindowHeight), 0.0f, 1.0f }; + mCommandList->RSSetViewports(1, &viewport); + + // Set scissor rect + D3D12_RECT scissor_rect = { 0, 0, static_cast(mWindowWidth), static_cast(mWindowHeight) }; + mCommandList->RSSetScissorRects(1, &scissor_rect); +} + +void RendererDX12::EndFrame() +{ + JPH_PROFILE_FUNCTION(); + + Renderer::EndFrame(); + + // Indicate that the back buffer will now be used to present. + D3D12_RESOURCE_BARRIER barrier; + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; + barrier.Transition.pResource = mRenderTargets[mFrameIndex].Get(); + barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT; + barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + mCommandList->ResourceBarrier(1, &barrier); + + // Close the command list + FatalErrorIfFailed(mCommandList->Close()); + + // Execute the command list + ID3D12CommandList* command_lists[] = { mCommandList.Get() }; + mCommandQueue->ExecuteCommandLists(_countof(command_lists), command_lists); + + // Present the frame + FatalErrorIfFailed(mSwapChain->Present(1, 0)); + + // Schedule a Signal command in the queue + UINT64 current_fence_value = mFenceValues[mFrameIndex]; + FatalErrorIfFailed(mCommandQueue->Signal(mFence.Get(), current_fence_value)); + + // Update the frame index + mFrameIndex = mSwapChain->GetCurrentBackBufferIndex(); + + // If the next frame is not ready to be rendered yet, wait until it is ready + UINT64 completed_value = mFence->GetCompletedValue(); + if (completed_value < mFenceValues[mFrameIndex]) + { + FatalErrorIfFailed(mFence->SetEventOnCompletion(mFenceValues[mFrameIndex], mFenceEvent)); + WaitForSingleObjectEx(mFenceEvent, INFINITE, FALSE); + } + + // Release all used resources + mDelayReleased[mFrameIndex].clear(); + + // Anything that's not used yet can be removed, delayed objects are now available + mResourceCache.clear(); + mDelayCached[mFrameIndex].swap(mResourceCache); + + // Set the fence value for the next frame. + mFenceValues[mFrameIndex] = current_fence_value + 1; +} + + +void RendererDX12::SetProjectionMode() +{ + JPH_ASSERT(mInFrame); + + mVertexShaderConstantBufferProjection[mFrameIndex]->Bind(0); +} + +void RendererDX12::SetOrthoMode() +{ + JPH_ASSERT(mInFrame); + + mVertexShaderConstantBufferOrtho[mFrameIndex]->Bind(0); +} + +Ref RendererDX12::CreateTexture(const Surface *inSurface) +{ + return new TextureDX12(this, inSurface); +} + +Ref RendererDX12::CreateVertexShader(const char *inFileName) +{ + UINT flags = D3DCOMPILE_ENABLE_STRICTNESS; +#ifdef JPH_DEBUG + flags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; +#endif + + const D3D_SHADER_MACRO defines[] = + { + { nullptr, nullptr } + }; + + // Read shader source file + Array data = ReadData((String(inFileName) + ".hlsl").c_str()); + + // Compile source + ComPtr shader_blob, error_blob; + HRESULT hr = D3DCompile(&data[0], + (uint)data.size(), + inFileName, + defines, + D3D_COMPILE_STANDARD_FILE_INCLUDE, + "main", + "vs_5_0", + flags, + 0, + shader_blob.GetAddressOf(), + error_blob.GetAddressOf()); + if (FAILED(hr)) + { + // Throw error if compilation failed + if (error_blob) + OutputDebugStringA((const char *)error_blob->GetBufferPointer()); + FatalError("Failed to compile vertex shader"); + } + + return new VertexShaderDX12(shader_blob); +} + +Ref RendererDX12::CreatePixelShader(const char *inFileName) +{ + UINT flags = D3DCOMPILE_ENABLE_STRICTNESS; +#ifdef JPH_DEBUG + flags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; +#endif + + const D3D_SHADER_MACRO defines[] = + { + { nullptr, nullptr } + }; + + // Read shader source file + Array data = ReadData((String(inFileName) + ".hlsl").c_str()); + + // Compile source + ComPtr shader_blob, error_blob; + HRESULT hr = D3DCompile(&data[0], + (uint)data.size(), + inFileName, + defines, + D3D_COMPILE_STANDARD_FILE_INCLUDE, + "main", + "ps_5_0", + flags, + 0, + shader_blob.GetAddressOf(), + error_blob.GetAddressOf()); + if (FAILED(hr)) + { + // Throw error if compilation failed + if (error_blob) + OutputDebugStringA((const char *)error_blob->GetBufferPointer()); + FatalError("Failed to compile pixel shader"); + } + + return new PixelShaderDX12(shader_blob); +} + +unique_ptr RendererDX12::CreateConstantBuffer(uint inBufferSize) +{ + return make_unique(this, inBufferSize); +} + +unique_ptr RendererDX12::CreatePipelineState(const VertexShader *inVertexShader, const PipelineState::EInputDescription *inInputDescription, uint inInputDescriptionCount, const PixelShader *inPixelShader, PipelineState::EDrawPass inDrawPass, PipelineState::EFillMode inFillMode, PipelineState::ETopology inTopology, PipelineState::EDepthTest inDepthTest, PipelineState::EBlendMode inBlendMode, PipelineState::ECullMode inCullMode) +{ + return make_unique(this, static_cast(inVertexShader), inInputDescription, inInputDescriptionCount, static_cast(inPixelShader), inDrawPass, inFillMode, inTopology, inDepthTest, inBlendMode, inCullMode); +} + +RenderPrimitive *RendererDX12::CreateRenderPrimitive(PipelineState::ETopology inType) +{ + return new RenderPrimitiveDX12(this, inType); +} + +RenderInstances *RendererDX12::CreateRenderInstances() +{ + return new RenderInstancesDX12(this); +} + +ComPtr RendererDX12::CreateD3DResource(D3D12_HEAP_TYPE inHeapType, D3D12_RESOURCE_STATES inResourceState, uint64 inSize) +{ + // Create a new resource + D3D12_RESOURCE_DESC desc; + desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + desc.Alignment = 0; + desc.Width = inSize; + desc.Height = 1; + desc.DepthOrArraySize = 1; + desc.MipLevels = 1; + desc.Format = DXGI_FORMAT_UNKNOWN; + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; + desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + desc.Flags = D3D12_RESOURCE_FLAG_NONE; + + D3D12_HEAP_PROPERTIES heap_properties = {}; + heap_properties.Type = inHeapType; + heap_properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + heap_properties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + heap_properties.CreationNodeMask = 1; + heap_properties.VisibleNodeMask = 1; + + ComPtr resource; + FatalErrorIfFailed(mDevice->CreateCommittedResource(&heap_properties, D3D12_HEAP_FLAG_NONE, &desc, inResourceState, nullptr, IID_PPV_ARGS(&resource))); + return resource; +} + +void RendererDX12::CopyD3DResource(ID3D12Resource *inDest, const void *inSrc, uint64 inSize) +{ + // Copy data to destination buffer + void *data; + D3D12_RANGE range = { 0, 0 }; // We're not going to read + FatalErrorIfFailed(inDest->Map(0, &range, &data)); + memcpy(data, inSrc, size_t(inSize)); + inDest->Unmap(0, nullptr); +} + +void RendererDX12::CopyD3DResource(ID3D12Resource *inDest, ID3D12Resource *inSrc, uint64 inSize) +{ + // Start a commandlist for the upload + ID3D12GraphicsCommandList *list = mUploadQueue.Start(); + + // Copy the data to the GPU + list->CopyBufferRegion(inDest, 0, inSrc, 0, inSize); + + // Change the state of the resource to generic read + D3D12_RESOURCE_BARRIER barrier; + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; + barrier.Transition.pResource = inDest; + barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_GENERIC_READ; + barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + list->ResourceBarrier(1, &barrier); + + // Wait for copying to finish + mUploadQueue.ExecuteAndWait(); +} + +ComPtr RendererDX12::CreateD3DResourceOnDefaultHeap(const void *inData, uint64 inSize) +{ + ComPtr upload = CreateD3DResourceOnUploadHeap(inSize); + ComPtr resource = CreateD3DResource(D3D12_HEAP_TYPE_DEFAULT, D3D12_RESOURCE_STATE_COMMON, inSize); + CopyD3DResource(upload.Get(), inData, inSize); + CopyD3DResource(resource.Get(), upload.Get(), inSize); + RecycleD3DResourceOnUploadHeap(upload.Get(), inSize); + return resource; +} + +ComPtr RendererDX12::CreateD3DResourceOnUploadHeap(uint64 inSize) +{ + // Try cache first + ResourceCache::iterator i = mResourceCache.find(inSize); + if (i != mResourceCache.end() && !i->second.empty()) + { + ComPtr resource = i->second.back(); + i->second.pop_back(); + return resource; + } + + return CreateD3DResource(D3D12_HEAP_TYPE_UPLOAD, D3D12_RESOURCE_STATE_GENERIC_READ, inSize); +} + +void RendererDX12::RecycleD3DResourceOnUploadHeap(ID3D12Resource *inResource, uint64 inSize) +{ + if (!mIsExiting) + mDelayCached[mFrameIndex][inSize].push_back(inResource); +} + +void RendererDX12::RecycleD3DObject(ID3D12Object *inResource) +{ + if (!mIsExiting) + mDelayReleased[mFrameIndex].push_back(inResource); +} diff --git a/TestFramework/Renderer/DX12/RendererDX12.h b/TestFramework/Renderer/DX12/RendererDX12.h new file mode 100644 index 000000000..b9de96fdd --- /dev/null +++ b/TestFramework/Renderer/DX12/RendererDX12.h @@ -0,0 +1,111 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include +#include +#include +#include +#include + +/// DirectX 12 renderer +class RendererDX12 : public Renderer +{ +public: + /// Destructor + virtual ~RendererDX12() override; + + // See: Renderer + virtual void Initialize() override; + virtual void BeginFrame(const CameraState &inCamera, float inWorldScale) override; + virtual void EndShadowPass() override; + virtual void EndFrame() override; + virtual void SetProjectionMode() override; + virtual void SetOrthoMode() override; + virtual Ref CreateTexture(const Surface *inSurface) override; + virtual Ref CreateVertexShader(const char *inFileName) override; + virtual Ref CreatePixelShader(const char *inFileName) override; + virtual unique_ptr CreatePipelineState(const VertexShader *inVertexShader, const PipelineState::EInputDescription *inInputDescription, uint inInputDescriptionCount, const PixelShader *inPixelShader, PipelineState::EDrawPass inDrawPass, PipelineState::EFillMode inFillMode, PipelineState::ETopology inTopology, PipelineState::EDepthTest inDepthTest, PipelineState::EBlendMode inBlendMode, PipelineState::ECullMode inCullMode) override; + virtual RenderPrimitive * CreateRenderPrimitive(PipelineState::ETopology inType) override; + virtual RenderInstances * CreateRenderInstances() override; + virtual Texture * GetShadowMap() const override { return mShadowMap.GetPtr(); } + virtual void OnWindowResize() override; + + /// Create a constant buffer + unique_ptr CreateConstantBuffer(uint inBufferSize); + + /// Create a buffer on the default heap (usable for permanent buffers) + ComPtr CreateD3DResourceOnDefaultHeap(const void *inData, uint64 inSize); + + /// Create buffer on the upload heap (usable for temporary buffers). + ComPtr CreateD3DResourceOnUploadHeap(uint64 inSize); + + /// Recycle a buffer on the upload heap. This puts it back in a cache and will reuse it when it is certain the GPU is no longer referencing it. + void RecycleD3DResourceOnUploadHeap(ID3D12Resource *inResource, uint64 inSize); + + /// Keeps a reference to the resource until the current frame has finished + void RecycleD3DObject(ID3D12Object *inResource); + + /// Access to the most important DirectX structures + ID3D12Device * GetDevice() { return mDevice.Get(); } + ID3D12RootSignature * GetRootSignature() { return mRootSignature.Get(); } + ID3D12GraphicsCommandList * GetCommandList() { JPH_ASSERT(mInFrame); return mCommandList.Get(); } + CommandQueueDX12 & GetUploadQueue() { return mUploadQueue; } + DescriptorHeapDX12 & GetDSVHeap() { return mDSVHeap; } + DescriptorHeapDX12 & GetSRVHeap() { return mSRVHeap; } + +private: + // Wait for pending GPU work to complete + void WaitForGpu(); + + // Create render targets and their views + void CreateRenderTargets(); + + // Create a depth buffer for the back buffer + void CreateDepthBuffer(); + + // Function to create a ID3D12Resource on specified heap with specified state + ComPtr CreateD3DResource(D3D12_HEAP_TYPE inHeapType, D3D12_RESOURCE_STATES inResourceState, uint64 inSize); + + // Copy CPU memory into a ID3D12Resource + void CopyD3DResource(ID3D12Resource *inDest, const void *inSrc, uint64 inSize); + + // Copy a CPU resource to a GPU resource + void CopyD3DResource(ID3D12Resource *inDest, ID3D12Resource *inSrc, uint64 inSize); + + // DirectX interfaces + ComPtr mDXGIFactory; + ComPtr mDevice; + DescriptorHeapDX12 mRTVHeap; ///< Render target view heap + DescriptorHeapDX12 mDSVHeap; ///< Depth stencil view heap + DescriptorHeapDX12 mSRVHeap; ///< Shader resource view heap + ComPtr mSwapChain; + ComPtr mRenderTargets[cFrameCount]; ///< Two render targets (we're double buffering in order for the CPU to continue while the GPU is rendering) + D3D12_CPU_DESCRIPTOR_HANDLE mRenderTargetViews[cFrameCount]; ///< The two render views corresponding to the render targets + ComPtr mDepthStencilBuffer; ///< The main depth buffer + D3D12_CPU_DESCRIPTOR_HANDLE mDepthStencilView { 0 }; ///< A view for binding the depth buffer + ComPtr mCommandAllocators[cFrameCount]; ///< Two command allocator lists (one per frame) + ComPtr mCommandQueue; ///< The command queue that will execute commands (there's only 1 since we want to finish rendering 1 frame before moving onto the next) + ComPtr mCommandList; ///< The command list + ComPtr mRootSignature; ///< The root signature, we have a simple application so we only need 1, which is suitable for all our shaders + Ref mShadowMap; ///< Used to render shadow maps + CommandQueueDX12 mUploadQueue; ///< Queue used to upload resources to GPU memory + unique_ptr mVertexShaderConstantBufferProjection[cFrameCount]; + unique_ptr mVertexShaderConstantBufferOrtho[cFrameCount]; + unique_ptr mPixelShaderConstantBuffer[cFrameCount]; + + // Synchronization objects used to finish rendering and swapping before reusing a command queue + HANDLE mFenceEvent; ///< Fence event to wait for the previous frame rendering to complete (in order to free 1 of the buffers) + ComPtr mFence; ///< Fence object, used to signal the end of a frame + UINT64 mFenceValues[cFrameCount] = {}; ///< Values that were used to signal completion of one of the two frames + + using ResourceCache = UnorderedMap>>; + + ResourceCache mResourceCache; ///< Cache items ready to be reused + ResourceCache mDelayCached[cFrameCount]; ///< List of reusable ID3D12Resources that are potentially referenced by the GPU so can be used only when the GPU finishes + Array> mDelayReleased[cFrameCount]; ///< Objects that are potentially referenced by the GPU so can only be freed when the GPU finishes + bool mIsExiting = false; ///< When exiting we don't want to add references too buffers +}; diff --git a/TestFramework/Renderer/Texture.cpp b/TestFramework/Renderer/DX12/TextureDX12.cpp similarity index 91% rename from TestFramework/Renderer/Texture.cpp rename to TestFramework/Renderer/DX12/TextureDX12.cpp index ef8eb0b80..533ce68ed 100644 --- a/TestFramework/Renderer/Texture.cpp +++ b/TestFramework/Renderer/DX12/TextureDX12.cpp @@ -1,28 +1,25 @@ // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) -// SPDX-FileCopyrightText: 2021 Jorrit Rouwe +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe // SPDX-License-Identifier: MIT #include -#include +#include +#include +#include #include -#include -#include -Texture::Texture(Renderer *inRenderer, const Surface *inSurface) : +TextureDX12::TextureDX12(RendererDX12 *inRenderer, const Surface *inSurface) : + Texture(inSurface->GetWidth(), inSurface->GetHeight()), mRenderer(inRenderer) { - // Store dimensions - mWidth = inSurface->GetWidth(); - mHeight = inSurface->GetHeight(); - // Create description D3D12_RESOURCE_DESC desc = {}; desc.MipLevels = 1; ESurfaceFormat format = inSurface->GetFormat(); switch (format) { - case ESurfaceFormat::A4L4: desc.Format = DXGI_FORMAT_R8G8_UNORM; break; + case ESurfaceFormat::A4L4: desc.Format = DXGI_FORMAT_R8G8_UNORM; format = ESurfaceFormat::A8L8; break; case ESurfaceFormat::L8: desc.Format = DXGI_FORMAT_R8_UNORM; break; case ESurfaceFormat::A8: desc.Format = DXGI_FORMAT_A8_UNORM; break; case ESurfaceFormat::A8L8: desc.Format = DXGI_FORMAT_R8G8_UNORM; break; @@ -128,13 +125,10 @@ Texture::Texture(Renderer *inRenderer, const Surface *inSurface) : inRenderer->RecycleD3DResourceOnUploadHeap(upload_resource.Get(), required_size); } -Texture::Texture(Renderer *inRenderer, int inWidth, int inHeight) : +TextureDX12::TextureDX12(RendererDX12 *inRenderer, int inWidth, int inHeight) : + Texture(inWidth, inHeight), mRenderer(inRenderer) { - // Store dimensions - mWidth = inWidth; - mHeight = inHeight; - // Allocate depth stencil buffer D3D12_CLEAR_VALUE clear_value = {}; clear_value.Format = DXGI_FORMAT_D32_FLOAT; @@ -182,7 +176,7 @@ Texture::Texture(Renderer *inRenderer, int inWidth, int inHeight) : inRenderer->GetDevice()->CreateShaderResourceView(mTexture.Get(), &srv_desc, mSRV); } -Texture::~Texture() +TextureDX12::~TextureDX12() { if (mSRV.ptr != 0) mRenderer->GetSRVHeap().Free(mSRV); @@ -194,17 +188,12 @@ Texture::~Texture() mRenderer->RecycleD3DObject(mTexture.Get()); } -void Texture::Bind(int inSlot) const -{ - mRenderer->GetCommandList()->SetGraphicsRootDescriptorTable(inSlot, mRenderer->GetSRVHeap().ConvertToGPUHandle(mSRV)); -} - -void Texture::ClearRenderTarget() +void TextureDX12::Bind() const { - mRenderer->GetCommandList()->ClearDepthStencilView(mDSV, D3D12_CLEAR_FLAG_DEPTH, 0, 0, 0, nullptr); + mRenderer->GetCommandList()->SetGraphicsRootDescriptorTable(2 /* All shaders use slot 2 to bind their texture */, mRenderer->GetSRVHeap().ConvertToGPUHandle(mSRV)); } -void Texture::SetAsRenderTarget(bool inSet) const +void TextureDX12::SetAsRenderTarget(bool inSet) const { if (inSet) { @@ -228,6 +217,9 @@ void Texture::SetAsRenderTarget(bool inSet) const // Set scissor rect D3D12_RECT scissor_rect = { 0, 0, static_cast(mWidth), static_cast(mHeight) }; mRenderer->GetCommandList()->RSSetScissorRects(1, &scissor_rect); + + // Clear the render target + mRenderer->GetCommandList()->ClearDepthStencilView(mDSV, D3D12_CLEAR_FLAG_DEPTH, 0, 0, 0, nullptr); } else { diff --git a/TestFramework/Renderer/DX12/TextureDX12.h b/TestFramework/Renderer/DX12/TextureDX12.h new file mode 100644 index 000000000..104646774 --- /dev/null +++ b/TestFramework/Renderer/DX12/TextureDX12.h @@ -0,0 +1,32 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +class RendererDX12; + +class TextureDX12 : public Texture +{ +public: + /// Constructor, called by Renderer::CreateTextureDX12 + TextureDX12(RendererDX12 *inRenderer, const Surface *inSurface); // Create a normal TextureDX12 + TextureDX12(RendererDX12 *inRenderer, int inWidth, int inHeight); // Create a render target (depth only) + virtual ~TextureDX12() override; + + /// Bind texture to the pixel shader + virtual void Bind() const override; + + /// Activate this texture as the current render target, used by RendererDX12::BeginFrame, EndShadowPass + void SetAsRenderTarget(bool inSet) const; + +private: + RendererDX12 * mRenderer; + + ComPtr mTexture; ///< The texture data + + D3D12_CPU_DESCRIPTOR_HANDLE mSRV { 0 }; ///< Shader resource view to bind as texture + D3D12_CPU_DESCRIPTOR_HANDLE mDSV { 0 }; ///< Depth shader view to bind as render target +}; diff --git a/TestFramework/Renderer/DX12/VertexShaderDX12.h b/TestFramework/Renderer/DX12/VertexShaderDX12.h new file mode 100644 index 000000000..f27c5b2ba --- /dev/null +++ b/TestFramework/Renderer/DX12/VertexShaderDX12.h @@ -0,0 +1,17 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +/// Vertex shader handle for DirectX +class VertexShaderDX12 : public VertexShader +{ +public: + /// Constructor + VertexShaderDX12(ComPtr inShader) : mShader(inShader) { } + + ComPtr mShader; ///< The compiled shader +}; diff --git a/TestFramework/Renderer/DebugRendererImp.cpp b/TestFramework/Renderer/DebugRendererImp.cpp index 4cedfaf3b..0ca792991 100644 --- a/TestFramework/Renderer/DebugRendererImp.cpp +++ b/TestFramework/Renderer/DebugRendererImp.cpp @@ -6,8 +6,6 @@ #include #include -#include -#include #include #ifndef JPH_DEBUG_RENDERER @@ -22,55 +20,46 @@ DebugRendererImp::DebugRendererImp(Renderer *inRenderer, const Font *inFont) : mFont(inFont) { // Create input layout for lines - const D3D12_INPUT_ELEMENT_DESC line_vertex_desc[] = + const PipelineState::EInputDescription line_vertex_desc[] = { - { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, - { "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, + PipelineState::EInputDescription::Position, + PipelineState::EInputDescription::Color }; // Lines - ComPtr vtx_line = mRenderer->CreateVertexShader("Assets/Shaders/LineVertexShader.hlsl"); - ComPtr pix_line = mRenderer->CreatePixelShader("Assets/Shaders/LinePixelShader.hlsl"); - mLineState = mRenderer->CreatePipelineState(vtx_line.Get(), line_vertex_desc, ARRAYSIZE(line_vertex_desc), pix_line.Get(), D3D12_FILL_MODE_SOLID, D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE, PipelineState::EDepthTest::On, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface); + Ref vtx_line = mRenderer->CreateVertexShader("Assets/Shaders/LineVertexShader"); + Ref pix_line = mRenderer->CreatePixelShader("Assets/Shaders/LinePixelShader"); + mLineState = mRenderer->CreatePipelineState(vtx_line, line_vertex_desc, std::size(line_vertex_desc), pix_line, PipelineState::EDrawPass::Normal, PipelineState::EFillMode::Solid, PipelineState::ETopology::Line, PipelineState::EDepthTest::On, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface); // Create input layout for triangles - const D3D12_INPUT_ELEMENT_DESC triangles_vertex_desc[] = + const PipelineState::EInputDescription triangles_vertex_desc[] = { - { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, - { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, - { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, - { "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, 32, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, - { "INSTANCE_TRANSFORM", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 0, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 }, - { "INSTANCE_TRANSFORM", 1, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 16, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 }, - { "INSTANCE_TRANSFORM", 2, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 32, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 }, - { "INSTANCE_TRANSFORM", 3, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 48, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 }, - { "INSTANCE_INV_TRANSFORM", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 64, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 }, - { "INSTANCE_INV_TRANSFORM", 1, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 80, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 }, - { "INSTANCE_INV_TRANSFORM", 2, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 96, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 }, - { "INSTANCE_INV_TRANSFORM", 3, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 112, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 }, - { "INSTANCE_COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 1, 128, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 }, + PipelineState::EInputDescription::Position, + PipelineState::EInputDescription::Normal, + PipelineState::EInputDescription::TexCoord, + PipelineState::EInputDescription::Color, + PipelineState::EInputDescription::InstanceTransform, + PipelineState::EInputDescription::InstanceInvTransform, + PipelineState::EInputDescription::InstanceColor }; // Triangles - ComPtr vtx_triangle = mRenderer->CreateVertexShader("Assets/Shaders/TriangleVertexShader.hlsl"); - ComPtr pix_triangle = mRenderer->CreatePixelShader("Assets/Shaders/TrianglePixelShader.hlsl"); - mTriangleStateBF = mRenderer->CreatePipelineState(vtx_triangle.Get(), triangles_vertex_desc, ARRAYSIZE(triangles_vertex_desc), pix_triangle.Get(), D3D12_FILL_MODE_SOLID, D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE, PipelineState::EDepthTest::On, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface); - mTriangleStateFF = mRenderer->CreatePipelineState(vtx_triangle.Get(), triangles_vertex_desc, ARRAYSIZE(triangles_vertex_desc), pix_triangle.Get(), D3D12_FILL_MODE_SOLID, D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE, PipelineState::EDepthTest::On, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::FrontFace); - mTriangleStateWire = mRenderer->CreatePipelineState(vtx_triangle.Get(), triangles_vertex_desc, ARRAYSIZE(triangles_vertex_desc), pix_triangle.Get(), D3D12_FILL_MODE_WIREFRAME, D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE, PipelineState::EDepthTest::On, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface); + Ref vtx_triangle = mRenderer->CreateVertexShader("Assets/Shaders/TriangleVertexShader"); + Ref pix_triangle = mRenderer->CreatePixelShader("Assets/Shaders/TrianglePixelShader"); + mTriangleStateBF = mRenderer->CreatePipelineState(vtx_triangle, triangles_vertex_desc, std::size(triangles_vertex_desc), pix_triangle, PipelineState::EDrawPass::Normal, PipelineState::EFillMode::Solid, PipelineState::ETopology::Triangle, PipelineState::EDepthTest::On, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface); + mTriangleStateFF = mRenderer->CreatePipelineState(vtx_triangle, triangles_vertex_desc, std::size(triangles_vertex_desc), pix_triangle, PipelineState::EDrawPass::Normal, PipelineState::EFillMode::Solid, PipelineState::ETopology::Triangle, PipelineState::EDepthTest::On, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::FrontFace); + mTriangleStateWire = mRenderer->CreatePipelineState(vtx_triangle, triangles_vertex_desc, std::size(triangles_vertex_desc), pix_triangle, PipelineState::EDrawPass::Normal, PipelineState::EFillMode::Wireframe, PipelineState::ETopology::Triangle, PipelineState::EDepthTest::On, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface); // Shadow pass - ComPtr vtx_shadow = mRenderer->CreateVertexShader("Assets/Shaders/TriangleDepthVertexShader.hlsl"); - ComPtr pix_shadow = mRenderer->CreatePixelShader("Assets/Shaders/TriangleDepthPixelShader.hlsl"); - mShadowStateBF = mRenderer->CreatePipelineState(vtx_shadow.Get(), triangles_vertex_desc, ARRAYSIZE(triangles_vertex_desc), pix_shadow.Get(), D3D12_FILL_MODE_SOLID, D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE, PipelineState::EDepthTest::On, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface); - mShadowStateFF = mRenderer->CreatePipelineState(vtx_shadow.Get(), triangles_vertex_desc, ARRAYSIZE(triangles_vertex_desc), pix_shadow.Get(), D3D12_FILL_MODE_SOLID, D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE, PipelineState::EDepthTest::On, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::FrontFace); - mShadowStateWire = mRenderer->CreatePipelineState(vtx_shadow.Get(), triangles_vertex_desc, ARRAYSIZE(triangles_vertex_desc), pix_shadow.Get(), D3D12_FILL_MODE_WIREFRAME, D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE, PipelineState::EDepthTest::On, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface); - - // Create depth only texture (no color buffer, as seen from light) - mDepthTexture = mRenderer->CreateRenderTarget(4096, 4096); + Ref vtx_shadow = mRenderer->CreateVertexShader("Assets/Shaders/TriangleDepthVertexShader"); + Ref pix_shadow = mRenderer->CreatePixelShader("Assets/Shaders/TriangleDepthPixelShader"); + mShadowStateBF = mRenderer->CreatePipelineState(vtx_shadow, triangles_vertex_desc, std::size(triangles_vertex_desc), pix_shadow, PipelineState::EDrawPass::Shadow, PipelineState::EFillMode::Solid, PipelineState::ETopology::Triangle, PipelineState::EDepthTest::On, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface); + mShadowStateFF = mRenderer->CreatePipelineState(vtx_shadow, triangles_vertex_desc, std::size(triangles_vertex_desc), pix_shadow, PipelineState::EDrawPass::Shadow, PipelineState::EFillMode::Solid, PipelineState::ETopology::Triangle, PipelineState::EDepthTest::On, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::FrontFace); + mShadowStateWire = mRenderer->CreatePipelineState(vtx_shadow, triangles_vertex_desc, std::size(triangles_vertex_desc), pix_shadow, PipelineState::EDrawPass::Shadow, PipelineState::EFillMode::Wireframe, PipelineState::ETopology::Triangle, PipelineState::EDepthTest::On, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface); // Create instances buffer for (uint n = 0; n < Renderer::cFrameCount; ++n) - mInstancesBuffer[n] = new RenderInstances(mRenderer); + mInstancesBuffer[n] = mRenderer->CreateRenderInstances(); // Create empty batch Vertex empty_vertex { Float3(0, 0, 0), Float3(1, 0, 0), Float2(0, 0), Color::sWhite }; @@ -100,7 +89,7 @@ DebugRenderer::Batch DebugRendererImp::CreateTriangleBatch(const Triangle *inTri if (inTriangles == nullptr || inTriangleCount == 0) return mEmptyBatch; - BatchImpl *primitive = new BatchImpl(mRenderer, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + RenderPrimitive *primitive = mRenderer->CreateRenderPrimitive(PipelineState::ETopology::Triangle); primitive->CreateVertexBuffer(3 * inTriangleCount, sizeof(Vertex), inTriangles); return primitive; @@ -111,7 +100,7 @@ DebugRenderer::Batch DebugRendererImp::CreateTriangleBatch(const Vertex *inVerti if (inVertices == nullptr || inVertexCount == 0 || inIndices == nullptr || inIndexCount == 0) return mEmptyBatch; - BatchImpl *primitive = new BatchImpl(mRenderer, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + RenderPrimitive *primitive = mRenderer->CreateRenderPrimitive(PipelineState::ETopology::Triangle); primitive->CreateVertexBuffer(inVertexCount, sizeof(Vertex), inVertices); primitive->CreateIndexBuffer(inIndexCount, inIndices); @@ -158,16 +147,14 @@ void DebugRendererImp::FinalizePrimitive() if (mLockedPrimitive != nullptr) { - BatchImpl *primitive = static_cast(mLockedPrimitive.GetPtr()); - // Unlock the primitive - primitive->UnlockVertexBuffer(); + mLockedPrimitive->UnlockVertexBuffer(); // Set number of indices to draw - primitive->SetNumVtxToDraw(int(mLockedVertices - mLockedVerticesStart)); + mLockedPrimitive->SetNumVtxToDraw(int(mLockedVertices - mLockedVerticesStart)); // Add to draw list - mTempPrimitives[new Geometry(mLockedPrimitive, mLockedPrimitiveBounds)].mInstances.push_back({ Mat44::sIdentity(), Mat44::sIdentity(), Color::sWhite, mLockedPrimitiveBounds, 1.0f }); + mTempPrimitives[new Geometry(mLockedPrimitive.GetPtr(), mLockedPrimitiveBounds)].mInstances.push_back({ Mat44::sIdentity(), Mat44::sIdentity(), Color::sWhite, mLockedPrimitiveBounds, 1.0f }); ++mNumInstances; // Clear pointers @@ -189,12 +176,11 @@ void DebugRendererImp::EnsurePrimitiveSpace(int inVtxSize) FinalizePrimitive(); // Create new - BatchImpl *primitive = new BatchImpl(mRenderer, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); - primitive->CreateVertexBuffer(cVertexBufferSize, sizeof(Vertex)); - mLockedPrimitive = primitive; - + mLockedPrimitive = mRenderer->CreateRenderPrimitive(PipelineState::ETopology::Triangle); + mLockedPrimitive->CreateVertexBuffer(cVertexBufferSize, sizeof(Vertex)); + // Lock buffers - mLockedVerticesStart = mLockedVertices = (Vertex *)primitive->LockVertexBuffer(); + mLockedVerticesStart = mLockedVertices = (Vertex *)mLockedPrimitive->LockVertexBuffer(); mLockedVerticesEnd = mLockedVertices + cVertexBufferSize; } } @@ -240,7 +226,7 @@ void DebugRendererImp::DrawInstances(const Geometry *inGeometry, const ArrayDraw(static_cast(geometry_lods[lod].mTriangleBatch.GetPtr()), start_idx, num_instances); + instances_buffer->Draw(static_cast(geometry_lods[lod].mTriangleBatch.GetPtr()), start_idx, num_instances); } } } @@ -264,17 +250,17 @@ void DebugRendererImp::DrawLines() // Draw the lines if (!mLines.empty()) { - RenderPrimitive primitive(mRenderer, D3D_PRIMITIVE_TOPOLOGY_LINELIST); - primitive.CreateVertexBuffer((int)mLines.size() * 2, sizeof(Line) / 2); - void *data = primitive.LockVertexBuffer(); + Ref primitive = mRenderer->CreateRenderPrimitive(PipelineState::ETopology::Line); + primitive->CreateVertexBuffer((int)mLines.size() * 2, sizeof(Line) / 2); + void *data = primitive->LockVertexBuffer(); memcpy(data, &mLines[0], mLines.size() * sizeof(Line)); - primitive.UnlockVertexBuffer(); + primitive->UnlockVertexBuffer(); mLineState->Activate(); - primitive.Draw(); + primitive->Draw(); } } -void DebugRendererImp::DrawTriangles() +void DebugRendererImp::DrawShadowPass() { JPH_PROFILE_FUNCTION(); @@ -283,12 +269,6 @@ void DebugRendererImp::DrawTriangles() // Finish the last primitive FinalizePrimitive(); - // Render to shadow map texture first - mRenderer->SetRenderTarget(mDepthTexture); - - // Clear the shadow map texture to max depth - mDepthTexture->ClearRenderTarget(); - // Get the camera and light frustum for culling Vec3 camera_pos(mRenderer->GetCameraState().mPos - mRenderer->GetBaseOffset()); const Frustum &camera_frustum = mRenderer->GetCameraFrustum(); @@ -411,12 +391,12 @@ void DebugRendererImp::DrawTriangles() for (InstanceMap::value_type &v : mWireframePrimitives) DrawInstances(v.first, v.second.mLightStartIdx); } +} - // Switch to the main render target - mRenderer->SetRenderTarget(nullptr); - +void DebugRendererImp::DrawTriangles() +{ // Bind the shadow map texture - mDepthTexture->Bind(2); + mRenderer->GetShadowMap()->Bind(); if (!mPrimitives.empty() || !mTempPrimitives.empty()) { diff --git a/TestFramework/Renderer/DebugRendererImp.h b/TestFramework/Renderer/DebugRendererImp.h index cdcbc5bc6..7a9843067 100644 --- a/TestFramework/Renderer/DebugRendererImp.h +++ b/TestFramework/Renderer/DebugRendererImp.h @@ -16,8 +16,7 @@ #undef JPH_DEBUG_RENDERER_EXPORT #endif -#include -#include +#include #include #include @@ -41,6 +40,9 @@ class DebugRendererImp final : public DebugRenderer virtual void DrawGeometry(RMat44Arg inModelMatrix, const AABox &inWorldSpaceBounds, float inLODScaleSq, ColorArg inModelColor, const GeometryRef &inGeometry, ECullMode inCullMode, ECastShadow inCastShadow, EDrawMode inDrawMode) override; virtual void DrawText3D(RVec3Arg inPosition, const string_view &inString, ColorArg inColor, float inHeight) override; + /// Draw all primitives from the light source + void DrawShadowPass(); + /// Draw all primitives that were added void Draw(); @@ -58,18 +60,6 @@ class DebugRendererImp final : public DebugRenderer void ClearTriangles(); void ClearTexts(); - /// Implementation specific batch object - class BatchImpl : public RefTargetVirtual, public RenderPrimitive - { - public: - JPH_OVERRIDE_NEW_DELETE - - BatchImpl(Renderer *inRenderer, D3D_PRIMITIVE_TOPOLOGY inType) : RenderPrimitive(inRenderer, inType) { } - - virtual void AddRef() override { RenderPrimitive::AddRef(); } - virtual void Release() override { if (--mRefCount == 0) delete this; } - }; - /// Finalize the current locked primitive and add it to the primitives to draw void FinalizePrimitive(); @@ -88,9 +78,6 @@ class DebugRendererImp final : public DebugRenderer unique_ptr mShadowStateFF; unique_ptr mShadowStateWire; - /// The shadow buffer (depth buffer rendered from the light) - Ref mDepthTexture; - /// Lock that protects the triangle batches from being accessed from multiple threads Mutex mPrimitivesLock; @@ -149,7 +136,7 @@ class DebugRendererImp final : public DebugRenderer Ref mInstancesBuffer[Renderer::cFrameCount]; /// Primitive that is being built + its properties - Batch mLockedPrimitive; + Ref mLockedPrimitive; Vertex * mLockedVerticesStart = nullptr; Vertex * mLockedVertices = nullptr; Vertex * mLockedVerticesEnd = nullptr; diff --git a/TestFramework/Renderer/Font.cpp b/TestFramework/Renderer/Font.cpp index 550cb57d9..330322c51 100644 --- a/TestFramework/Renderer/Font.cpp +++ b/TestFramework/Renderer/Font.cpp @@ -196,20 +196,20 @@ Font::Create(const char *inFontName, int inCharHeight) DeleteDC(dc); // Create input layout - const D3D12_INPUT_ELEMENT_DESC vertex_desc[] = + const PipelineState::EInputDescription vertex_desc[] = { - { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, - { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, - { "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, 20, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, + PipelineState::EInputDescription::Position, + PipelineState::EInputDescription::TexCoord, + PipelineState::EInputDescription::Color }; // Load vertex shader - ComPtr vtx = mRenderer->CreateVertexShader("Assets/Shaders/FontVertexShader.hlsl"); + Ref vtx = mRenderer->CreateVertexShader("Assets/Shaders/FontVertexShader"); // Load pixel shader - ComPtr pix = mRenderer->CreatePixelShader("Assets/Shaders/FontPixelShader.hlsl"); + Ref pix = mRenderer->CreatePixelShader("Assets/Shaders/FontPixelShader"); - mPipelineState = mRenderer->CreatePipelineState(vtx.Get(), vertex_desc, ARRAYSIZE(vertex_desc), pix.Get(), D3D12_FILL_MODE_SOLID, D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE, PipelineState::EDepthTest::Off, PipelineState::EBlendMode::AlphaTest, PipelineState::ECullMode::Backface); + mPipelineState = mRenderer->CreatePipelineState(vtx, vertex_desc, std::size(vertex_desc), pix, PipelineState::EDrawPass::Normal, PipelineState::EFillMode::Solid, PipelineState::ETopology::Triangle, PipelineState::EDepthTest::Off, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface); // Create texture mTexture = mRenderer->CreateTexture(surface); @@ -396,13 +396,13 @@ void Font::DrawText3D(Mat44Arg inTransform, const string_view &inText, ColorArg if (inText.empty()) return; - RenderPrimitive primitive(mRenderer, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); - if (CreateString(inTransform, inText, inColor, primitive)) + Ref primitive = mRenderer->CreateRenderPrimitive(PipelineState::ETopology::Triangle); + if (CreateString(inTransform, inText, inColor, *primitive)) { - mTexture->Bind(2); + mTexture->Bind(); mPipelineState->Activate(); - primitive.Draw(); + primitive->Draw(); } } diff --git a/TestFramework/Renderer/Font.h b/TestFramework/Renderer/Font.h index 1cde54e0f..5c606edd4 100644 --- a/TestFramework/Renderer/Font.h +++ b/TestFramework/Renderer/Font.h @@ -10,6 +10,7 @@ #include #include #include +#include class Renderer; class Texture; diff --git a/TestFramework/Renderer/PipelineState.cpp b/TestFramework/Renderer/PipelineState.cpp deleted file mode 100644 index 0d46a47db..000000000 --- a/TestFramework/Renderer/PipelineState.cpp +++ /dev/null @@ -1,84 +0,0 @@ -// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) -// SPDX-FileCopyrightText: 2021 Jorrit Rouwe -// SPDX-License-Identifier: MIT - -#include - -#include -#include -#include - -PipelineState::PipelineState(Renderer *inRenderer, ID3DBlob *inVertexShader, const D3D12_INPUT_ELEMENT_DESC *inInputDescription, uint inInputDescriptionCount, ID3DBlob *inPixelShader, D3D12_FILL_MODE inFillMode, D3D12_PRIMITIVE_TOPOLOGY_TYPE inTopology, EDepthTest inDepthTest, EBlendMode inBlendMode, ECullMode inCullMode) : - mRenderer(inRenderer) -{ - D3D12_GRAPHICS_PIPELINE_STATE_DESC pso_desc = {}; - pso_desc.InputLayout = { inInputDescription, inInputDescriptionCount }; - pso_desc.pRootSignature = mRenderer->GetRootSignature(); - pso_desc.VS = { inVertexShader->GetBufferPointer(), inVertexShader->GetBufferSize() }; - pso_desc.PS = { inPixelShader->GetBufferPointer(), inPixelShader->GetBufferSize() }; - - pso_desc.RasterizerState.FillMode = inFillMode; - pso_desc.RasterizerState.CullMode = inCullMode == ECullMode::Backface? D3D12_CULL_MODE_FRONT : D3D12_CULL_MODE_BACK; // DX uses left handed system so we reverse the options - pso_desc.RasterizerState.FrontCounterClockwise = FALSE; - pso_desc.RasterizerState.DepthBias = D3D12_DEFAULT_DEPTH_BIAS; - pso_desc.RasterizerState.DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP; - pso_desc.RasterizerState.SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS; - pso_desc.RasterizerState.DepthClipEnable = TRUE; - pso_desc.RasterizerState.MultisampleEnable = FALSE; - pso_desc.RasterizerState.AntialiasedLineEnable = FALSE; - pso_desc.RasterizerState.ForcedSampleCount = 0; - pso_desc.RasterizerState.ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF; - - pso_desc.BlendState.AlphaToCoverageEnable = FALSE; - pso_desc.BlendState.IndependentBlendEnable = FALSE; - - D3D12_RENDER_TARGET_BLEND_DESC &blend_desc = pso_desc.BlendState.RenderTarget[0]; - blend_desc.LogicOpEnable = FALSE; - blend_desc.LogicOp = D3D12_LOGIC_OP_NOOP; - blend_desc.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL; - switch (inBlendMode) - { - case EBlendMode::Write: - blend_desc.BlendEnable = FALSE; - break; - - case EBlendMode::AlphaTest: - pso_desc.BlendState.AlphaToCoverageEnable = TRUE; - [[fallthrough]]; - - case EBlendMode::AlphaBlend: - blend_desc.BlendEnable = TRUE; - blend_desc.SrcBlend = D3D12_BLEND_SRC_ALPHA; - blend_desc.DestBlend = D3D12_BLEND_INV_SRC_ALPHA; - blend_desc.BlendOp = D3D12_BLEND_OP_ADD; - blend_desc.SrcBlendAlpha = D3D12_BLEND_ZERO; - blend_desc.DestBlendAlpha = D3D12_BLEND_ZERO; - blend_desc.BlendOpAlpha = D3D12_BLEND_OP_ADD; - break; - } - - pso_desc.DepthStencilState.DepthEnable = inDepthTest == EDepthTest::On? TRUE : FALSE; - pso_desc.DepthStencilState.DepthWriteMask = inDepthTest == EDepthTest::On? D3D12_DEPTH_WRITE_MASK_ALL : D3D12_DEPTH_WRITE_MASK_ZERO; - pso_desc.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_GREATER; - pso_desc.DepthStencilState.StencilEnable = FALSE; - - pso_desc.SampleMask = UINT_MAX; - pso_desc.PrimitiveTopologyType = inTopology; - pso_desc.NumRenderTargets = 1; - pso_desc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM; - pso_desc.DSVFormat = DXGI_FORMAT_D32_FLOAT; - pso_desc.SampleDesc.Count = 1; - - FatalErrorIfFailed(mRenderer->GetDevice()->CreateGraphicsPipelineState(&pso_desc, IID_PPV_ARGS(&mPSO))); -} - -PipelineState::~PipelineState() -{ - if (mPSO != nullptr) - mRenderer->RecycleD3DObject(mPSO.Get()); -} - -void PipelineState::Activate() -{ - mRenderer->GetCommandList()->SetPipelineState(mPSO.Get()); -} diff --git a/TestFramework/Renderer/PipelineState.h b/TestFramework/Renderer/PipelineState.h index c4ab0bc7b..0d2dbd864 100644 --- a/TestFramework/Renderer/PipelineState.h +++ b/TestFramework/Renderer/PipelineState.h @@ -4,12 +4,43 @@ #pragma once -class Renderer; - /// Defines how primitives should be rendered class PipelineState { public: + /// Describes the input layout of the vertex shader + enum class EInputDescription + { + Position, ///< 3 float position + Color, ///< 4 uint8 color + Normal, ///< 3 float normal + TexCoord, ///< 2 float texture coordinate + InstanceColor, ///< 4 uint8 per instance color + InstanceTransform, ///< 4x4 float per instance transform + InstanceInvTransform, ///< 4x4 float per instance inverse transform + }; + + /// In which draw pass to use this pipeline state + enum class EDrawPass + { + Shadow, + Normal + }; + + /// The type of topology to emit + enum class ETopology + { + Triangle, + Line + }; + + /// Fill mode of the triangles + enum class EFillMode + { + Solid, + Wireframe + }; + /// If depth write / depth test is on enum class EDepthTest { @@ -22,7 +53,6 @@ class PipelineState { Write, AlphaBlend, - AlphaTest, ///< Alpha blend with alpha test enabled }; /// How to cull triangles @@ -32,16 +62,9 @@ class PipelineState FrontFace, }; - /// Constructor - PipelineState(Renderer *inRenderer, ID3DBlob *inVertexShader, const D3D12_INPUT_ELEMENT_DESC *inInputDescription, uint inInputDescriptionCount, ID3DBlob *inPixelShader, D3D12_FILL_MODE inFillMode, D3D12_PRIMITIVE_TOPOLOGY_TYPE inTopology, EDepthTest inDepthTest, EBlendMode inBlendMode, ECullMode inCullMode); - ~PipelineState(); + /// Destructor + virtual ~PipelineState() = default; /// Make this pipeline state active (any primitives rendered after this will use this state) - void Activate(); - -private: - friend class Renderer; - - Renderer * mRenderer; - ComPtr mPSO; + virtual void Activate() = 0; }; diff --git a/TestFramework/Renderer/PixelShader.h b/TestFramework/Renderer/PixelShader.h new file mode 100644 index 000000000..8b8e80448 --- /dev/null +++ b/TestFramework/Renderer/PixelShader.h @@ -0,0 +1,15 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +/// Pixel shader handle +class PixelShader : public RefTarget +{ +public: + /// Destructor + virtual ~PixelShader() = default; +}; diff --git a/TestFramework/Renderer/RenderInstances.h b/TestFramework/Renderer/RenderInstances.h index 2b302c8c5..59d55d1ba 100644 --- a/TestFramework/Renderer/RenderInstances.h +++ b/TestFramework/Renderer/RenderInstances.h @@ -4,7 +4,6 @@ #pragma once -#include #include class RenderPrimitive; @@ -13,25 +12,17 @@ class RenderPrimitive; class RenderInstances : public RefTarget { public: - /// Constructor - RenderInstances(Renderer *inRenderer) : mRenderer(inRenderer) { } - ~RenderInstances() { Clear(); } + /// Destructor + virtual ~RenderInstances() = default; /// Erase all instance data - void Clear(); + virtual void Clear() = 0; /// Instance buffer management functions - void CreateBuffer(int inNumInstances, int inInstanceSize); - void * Lock(); - void Unlock(); + virtual void CreateBuffer(int inNumInstances, int inInstanceSize) = 0; + virtual void * Lock() = 0; + virtual void Unlock() = 0; /// Draw the instances when context has been set by Renderer::BindShader - void Draw(RenderPrimitive *inPrimitive, int inStartInstance, int inNumInstances) const; - -private: - Renderer * mRenderer; - - ComPtr mInstanceBuffer; - int mInstanceBufferSize = 0; - int mInstanceSize = 0; + virtual void Draw(RenderPrimitive *inPrimitive, int inStartInstance, int inNumInstances) const = 0; }; diff --git a/TestFramework/Renderer/RenderPrimitive.cpp b/TestFramework/Renderer/RenderPrimitive.cpp index 392c44afe..9f170364f 100644 --- a/TestFramework/Renderer/RenderPrimitive.cpp +++ b/TestFramework/Renderer/RenderPrimitive.cpp @@ -5,39 +5,18 @@ #include #include -#include void RenderPrimitive::ReleaseVertexBuffer() { - if (mVtxBuffer != nullptr) - { - if (mVtxBufferInUploadHeap) - mRenderer->RecycleD3DResourceOnUploadHeap(mVtxBuffer.Get(), mNumVtx * mVtxSize); - else - mRenderer->RecycleD3DObject(mVtxBuffer.Get()); - mVtxBuffer = nullptr; - } - mNumVtx = 0; mNumVtxToDraw = 0; mVtxSize = 0; - mVtxBufferInUploadHeap = false; } void RenderPrimitive::ReleaseIndexBuffer() { - if (mIdxBuffer != nullptr) - { - if (mIdxBufferInUploadHeap) - mRenderer->RecycleD3DResourceOnUploadHeap(mIdxBuffer.Get(), mNumIdx * sizeof(uint32)); - else - mRenderer->RecycleD3DObject(mIdxBuffer.Get()); - mIdxBuffer = nullptr; - } - mNumIdx = 0; mNumIdxToDraw = 0; - mIdxBufferInUploadHeap = false; } void RenderPrimitive::Clear() @@ -50,115 +29,15 @@ void RenderPrimitive::CreateVertexBuffer(int inNumVtx, int inVtxSize, const void { ReleaseVertexBuffer(); - uint64 size = uint64(inNumVtx) * inVtxSize; - - if (inData != nullptr) - { - // Data provided, assume the buffer is static so allocate it on the GPU - mVtxBuffer = mRenderer->CreateD3DResourceOnDefaultHeap(inData, size); - mVtxBufferInUploadHeap = false; - } - else - { - // No data provided, create a buffer that will be uploaded to the GPU every time it is used - mVtxBuffer = mRenderer->CreateD3DResourceOnUploadHeap(size); - mVtxBufferInUploadHeap = true; - } - - JPH_IF_DEBUG(mVtxBuffer->SetName(L"Vertex Buffer");) - mNumVtx = inNumVtx; mNumVtxToDraw = inNumVtx; mVtxSize = inVtxSize; } -void *RenderPrimitive::LockVertexBuffer() -{ - void *mapped_resource; - D3D12_RANGE range = { 0, 0 }; - FatalErrorIfFailed(mVtxBuffer->Map(0, &range, &mapped_resource)); - return mapped_resource; -} - -void RenderPrimitive::UnlockVertexBuffer() -{ - mVtxBuffer->Unmap(0, nullptr); -} - void RenderPrimitive::CreateIndexBuffer(int inNumIdx, const uint32 *inData) { ReleaseIndexBuffer(); - uint64 size = uint64(inNumIdx) * sizeof(uint32); - - if (inData != nullptr) - { - // Data provided, assume the buffer is static so allocate it on the GPU - mIdxBuffer = mRenderer->CreateD3DResourceOnDefaultHeap(inData, size); - mIdxBufferInUploadHeap = false; - } - else - { - // No data provided, create a buffer that will be uploaded to the GPU every time it is used - mIdxBuffer = mRenderer->CreateD3DResourceOnUploadHeap(size); - mIdxBufferInUploadHeap = true; - } - - JPH_IF_DEBUG(mIdxBuffer->SetName(L"Index Buffer");) - mNumIdx = inNumIdx; mNumIdxToDraw = inNumIdx; } - -uint32 *RenderPrimitive::LockIndexBuffer() -{ - uint32 *mapped_resource; - D3D12_RANGE range = { 0, 0 }; - FatalErrorIfFailed(mIdxBuffer->Map(0, &range, (void **)&mapped_resource)); - return mapped_resource; -} - -void RenderPrimitive::UnlockIndexBuffer() -{ - mIdxBuffer->Unmap(0, nullptr); -} - -void RenderPrimitive::Draw() const -{ - ID3D12GraphicsCommandList *command_list = mRenderer->GetCommandList(); - - // Set topology - command_list->IASetPrimitiveTopology(mType); - - if (mIdxBuffer == nullptr) - { - // Set vertex buffer - D3D12_VERTEX_BUFFER_VIEW vb_view; - vb_view.BufferLocation = mVtxBuffer->GetGPUVirtualAddress(); - vb_view.StrideInBytes = mVtxSize; - vb_view.SizeInBytes = mNumVtxToDraw * mVtxSize; - command_list->IASetVertexBuffers(0, 1, &vb_view); - - // Draw the non indexed primitive - command_list->DrawInstanced(mNumVtxToDraw, 1, 0, 0); - } - else - { - // Set vertex buffer - D3D12_VERTEX_BUFFER_VIEW vb_view; - vb_view.BufferLocation = mVtxBuffer->GetGPUVirtualAddress(); - vb_view.StrideInBytes = mVtxSize; - vb_view.SizeInBytes = mNumVtx * mVtxSize; - command_list->IASetVertexBuffers(0, 1, &vb_view); - - // Set index buffer - D3D12_INDEX_BUFFER_VIEW ib_view; - ib_view.BufferLocation = mIdxBuffer->GetGPUVirtualAddress(); - ib_view.SizeInBytes = mNumIdxToDraw * sizeof(uint32); - ib_view.Format = DXGI_FORMAT_R32_UINT; - command_list->IASetIndexBuffer(&ib_view); - - // Draw indexed primitive - command_list->DrawIndexedInstanced(mNumIdxToDraw, 1, 0, 0, 0); - } -} diff --git a/TestFramework/Renderer/RenderPrimitive.h b/TestFramework/Renderer/RenderPrimitive.h index cb716175e..513b67f89 100644 --- a/TestFramework/Renderer/RenderPrimitive.h +++ b/TestFramework/Renderer/RenderPrimitive.h @@ -4,16 +4,14 @@ #pragma once -#include #include /// Simple wrapper around vertex and index buffers -class RenderPrimitive : public RefTarget +class RenderPrimitive : public RefTarget, public RefTargetVirtual { public: - /// Constructor - RenderPrimitive(Renderer *inRenderer, D3D_PRIMITIVE_TOPOLOGY inType) : mRenderer(inRenderer), mType(inType) { } - ~RenderPrimitive() { Clear(); } + /// Destructor + virtual ~RenderPrimitive() override = default; /// Erase all primitive data void Clear(); @@ -22,41 +20,35 @@ class RenderPrimitive : public RefTarget bool IsEmpty() const { return mNumVtx == 0 && mNumIdx == 0; } /// Vertex buffer management functions - void CreateVertexBuffer(int inNumVtx, int inVtxSize, const void *inData = nullptr); - void ReleaseVertexBuffer(); - void * LockVertexBuffer(); - void UnlockVertexBuffer(); + virtual void CreateVertexBuffer(int inNumVtx, int inVtxSize, const void *inData = nullptr) = 0; + virtual void ReleaseVertexBuffer(); + virtual void * LockVertexBuffer() = 0; + virtual void UnlockVertexBuffer() = 0; int GetNumVtx() const { return mNumVtx; } int GetNumVtxToDraw() const { return mNumVtxToDraw; } void SetNumVtxToDraw(int inUsed) { mNumVtxToDraw = inUsed; } /// Index buffer management functions - void CreateIndexBuffer(int inNumIdx, const uint32 *inData = nullptr); - void ReleaseIndexBuffer(); - uint32 * LockIndexBuffer(); - void UnlockIndexBuffer(); + virtual void CreateIndexBuffer(int inNumIdx, const uint32 *inData = nullptr) = 0; + virtual void ReleaseIndexBuffer(); + virtual uint32 * LockIndexBuffer() = 0; + virtual void UnlockIndexBuffer() = 0; int GetNumIdx() const { return mNumIdx; } int GetNumIdxToDraw() const { return mNumIdxToDraw; } void SetNumIdxToDraw(int inUsed) { mNumIdxToDraw = inUsed; } /// Draw the primitive - void Draw() const; + virtual void Draw() const = 0; -private: - friend class RenderInstances; + /// Implement RefTargetVirtual, so we can conveniently use this class as DebugRenderer::Batch + virtual void AddRef() override { RefTarget::AddRef(); } + virtual void Release() override { RefTarget::Release(); } - Renderer * mRenderer; - - D3D_PRIMITIVE_TOPOLOGY mType; - - ComPtr mVtxBuffer; +protected: int mNumVtx = 0; int mNumVtxToDraw = 0; int mVtxSize = 0; - bool mVtxBufferInUploadHeap = false; - ComPtr mIdxBuffer; int mNumIdx = 0; int mNumIdxToDraw = 0; - bool mIdxBufferInUploadHeap = false; }; diff --git a/TestFramework/Renderer/Renderer.cpp b/TestFramework/Renderer/Renderer.cpp index def245d45..143e4b8ed 100644 --- a/TestFramework/Renderer/Renderer.cpp +++ b/TestFramework/Renderer/Renderer.cpp @@ -5,34 +5,12 @@ #include #include -#include -#include -#include -#include #include -#include #include -#ifdef JPH_DEBUG - #include -#endif static Renderer *sRenderer = nullptr; -struct VertexShaderConstantBuffer -{ - Mat44 mView; - Mat44 mProjection; - Mat44 mLightView; - Mat44 mLightProjection; -}; - -struct PixelShaderConstantBuffer -{ - Vec4 mCameraPos; - Vec4 mLightPos; -}; - //-------------------------------------------------------------------------------------- // Called every time the application receives a message //-------------------------------------------------------------------------------------- @@ -63,99 +41,6 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM l return 0; } -Renderer::~Renderer() -{ - // Ensure that the GPU is no longer referencing resources that are about to be cleaned up by the destructor. - WaitForGpu(); - - // Don't add more stuff to the delay reference list - mIsExiting = true; - - CloseHandle(mFenceEvent); -} - -void Renderer::WaitForGpu() -{ - // Schedule a Signal command in the queue - UINT64 current_fence_value = mFenceValues[mFrameIndex]; - FatalErrorIfFailed(mCommandQueue->Signal(mFence.Get(), current_fence_value)); - - // Wait until the fence has been processed - FatalErrorIfFailed(mFence->SetEventOnCompletion(current_fence_value, mFenceEvent)); - WaitForSingleObjectEx(mFenceEvent, INFINITE, FALSE); - - // Increment the fence value for all frames - for (uint n = 0; n < cFrameCount; ++n) - mFenceValues[n] = current_fence_value + 1; - - // Release all used resources - for (Array> &list : mDelayReleased) - list.clear(); - - // Anything that's not used yet can be removed, delayed objects are now available - mResourceCache.clear(); - mDelayCached[mFrameIndex].swap(mResourceCache); -} - -void Renderer::CreateRenterTargets() -{ - // Create render targets and views - for (uint n = 0; n < cFrameCount; ++n) - { - mRenderTargetViews[n] = mRTVHeap.Allocate(); - - FatalErrorIfFailed(mSwapChain->GetBuffer(n, IID_PPV_ARGS(&mRenderTargets[n]))); - mDevice->CreateRenderTargetView(mRenderTargets[n].Get(), nullptr, mRenderTargetViews[n]); - } -} - -void Renderer::CreateDepthBuffer() -{ - // Free any previous depth stencil view - if (mDepthStencilView.ptr != 0) - mDSVHeap.Free(mDepthStencilView); - - // Free any previous depth stencil buffer - mDepthStencilBuffer.Reset(); - - // Allocate depth stencil buffer - D3D12_CLEAR_VALUE clear_value = {}; - clear_value.Format = DXGI_FORMAT_D32_FLOAT; - clear_value.DepthStencil.Depth = 0.0f; - clear_value.DepthStencil.Stencil = 0; - - D3D12_HEAP_PROPERTIES heap_properties = {}; - heap_properties.Type = D3D12_HEAP_TYPE_DEFAULT; - heap_properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; - heap_properties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; - heap_properties.CreationNodeMask = 1; - heap_properties.VisibleNodeMask = 1; - - D3D12_RESOURCE_DESC depth_stencil_desc = {}; - depth_stencil_desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; - depth_stencil_desc.Alignment = 0; - depth_stencil_desc.Width = mWindowWidth; - depth_stencil_desc.Height = mWindowHeight; - depth_stencil_desc.DepthOrArraySize = 1; - depth_stencil_desc.MipLevels = 1; - depth_stencil_desc.Format = DXGI_FORMAT_D32_FLOAT; - depth_stencil_desc.SampleDesc.Count = 1; - depth_stencil_desc.SampleDesc.Quality = 0; - depth_stencil_desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; - depth_stencil_desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; - - FatalErrorIfFailed(mDevice->CreateCommittedResource(&heap_properties, D3D12_HEAP_FLAG_NONE, &depth_stencil_desc, D3D12_RESOURCE_STATE_DEPTH_WRITE, &clear_value, IID_PPV_ARGS(&mDepthStencilBuffer))); - - // Allocate depth stencil view - D3D12_DEPTH_STENCIL_VIEW_DESC depth_stencil_view_desc = {}; - depth_stencil_view_desc.Format = DXGI_FORMAT_D32_FLOAT; - depth_stencil_view_desc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D; - depth_stencil_view_desc.Flags = D3D12_DSV_FLAG_NONE; - - mDepthStencilView = mDSVHeap.Allocate(); - mDevice->CreateDepthStencilView(mDepthStencilBuffer.Get(), &depth_stencil_view_desc, mDepthStencilView); -} - void Renderer::Initialize() { // Prevent this window from auto scaling @@ -189,215 +74,6 @@ void Renderer::Initialize() // Show window ShowWindow(mhWnd, SW_SHOW); -#if defined(JPH_DEBUG) - // Enable the D3D12 debug layer - ComPtr debug_controller; - if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debug_controller)))) - debug_controller->EnableDebugLayer(); -#endif - - // Create DXGI factory - FatalErrorIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(&mDXGIFactory))); - - // Find adapter - ComPtr adapter; - - HRESULT result = E_FAIL; - - // First check if we have the Windows 1803 IDXGIFactory6 interface - ComPtr factory6; - if (SUCCEEDED(mDXGIFactory->QueryInterface(IID_PPV_ARGS(&factory6)))) - { - for (UINT index = 0; DXGI_ERROR_NOT_FOUND != factory6->EnumAdapterByGpuPreference(index, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, IID_PPV_ARGS(&adapter)); ++index) - { - DXGI_ADAPTER_DESC1 desc; - adapter->GetDesc1(&desc); - - // We don't want software renderers - if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) - continue; - - // Check to see whether the adapter supports Direct3D 12 - result = D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&mDevice)); - if (SUCCEEDED(result)) - break; - } - } - else - { - // Fall back to the older method that may not get the fastest GPU - for (UINT index = 0; DXGI_ERROR_NOT_FOUND != mDXGIFactory->EnumAdapters1(index, &adapter); ++index) - { - DXGI_ADAPTER_DESC1 desc; - adapter->GetDesc1(&desc); - - // We don't want software renderers - if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) - continue; - - // Check to see whether the adapter supports Direct3D 12 - result = D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&mDevice)); - if (SUCCEEDED(result)) - break; - } - } - - // Check if we managed to obtain a device - FatalErrorIfFailed(result); - -#ifdef JPH_DEBUG - // Enable breaking on errors - ComPtr info_queue; - if (SUCCEEDED(mDevice.As(&info_queue))) - { - info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, TRUE); - info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, TRUE); - info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, TRUE); - - // Disable an error that triggers on Windows 11 with a hybrid graphic system - // See: https://stackoverflow.com/questions/69805245/directx-12-application-is-crashing-in-windows-11 - D3D12_MESSAGE_ID hide[] = - { - D3D12_MESSAGE_ID_RESOURCE_BARRIER_MISMATCHING_COMMAND_LIST_TYPE, - }; - D3D12_INFO_QUEUE_FILTER filter = { }; - filter.DenyList.NumIDs = static_cast( std::size( hide ) ); - filter.DenyList.pIDList = hide; - info_queue->AddStorageFilterEntries( &filter ); - } -#endif // JPH_DEBUG - - // Disable full screen transitions - FatalErrorIfFailed(mDXGIFactory->MakeWindowAssociation(mhWnd, DXGI_MWA_NO_ALT_ENTER)); - - // Create heaps - mRTVHeap.Init(mDevice.Get(), D3D12_DESCRIPTOR_HEAP_TYPE_RTV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 2); - mDSVHeap.Init(mDevice.Get(), D3D12_DESCRIPTOR_HEAP_TYPE_DSV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 4); - mSRVHeap.Init(mDevice.Get(), D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE, 128); - - // Create a command queue - D3D12_COMMAND_QUEUE_DESC queue_desc = {}; - queue_desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; - queue_desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; - FatalErrorIfFailed(mDevice->CreateCommandQueue(&queue_desc, IID_PPV_ARGS(&mCommandQueue))); - - // Create a command allocator for each frame - for (uint n = 0; n < cFrameCount; n++) - FatalErrorIfFailed(mDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&mCommandAllocators[n]))); - - // Describe and create the swap chain - DXGI_SWAP_CHAIN_DESC swap_chain_desc = {}; - swap_chain_desc.BufferCount = cFrameCount; - swap_chain_desc.BufferDesc.Width = mWindowWidth; - swap_chain_desc.BufferDesc.Height = mWindowHeight; - swap_chain_desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; - swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; - swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; - swap_chain_desc.OutputWindow = mhWnd; - swap_chain_desc.SampleDesc.Count = 1; - swap_chain_desc.Windowed = TRUE; - - ComPtr swap_chain; - FatalErrorIfFailed(mDXGIFactory->CreateSwapChain(mCommandQueue.Get(), &swap_chain_desc, &swap_chain)); - FatalErrorIfFailed(swap_chain.As(&mSwapChain)); - mFrameIndex = mSwapChain->GetCurrentBackBufferIndex(); - - CreateRenterTargets(); - - CreateDepthBuffer(); - - // Create a root signature suitable for all our shaders - D3D12_ROOT_PARAMETER params[3] = {}; - - // Mapping a constant buffer to slot 0 for the vertex shader - params[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV; - params[0].Descriptor.ShaderRegister = 0; - params[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX; - - // Mapping a constant buffer to slot 1 in the pixel shader - params[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV; - params[1].Descriptor.ShaderRegister = 1; - params[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; - - // Mapping a texture to slot 2 in the pixel shader - D3D12_DESCRIPTOR_RANGE range = {}; - range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; - range.BaseShaderRegister = 2; - range.NumDescriptors = 1; - params[2].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; - params[2].DescriptorTable.NumDescriptorRanges = 1; - params[2].DescriptorTable.pDescriptorRanges = ⦥ - params[2].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; - - D3D12_STATIC_SAMPLER_DESC samplers[3] = {}; - - // Sampler 0: Non-wrapping linear filtering - samplers[0].Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR; - samplers[0].AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; - samplers[0].AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; - samplers[0].AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; - samplers[0].MipLODBias = 0.0f; - samplers[0].MaxAnisotropy = 1; - samplers[0].ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS; - samplers[0].BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK; - samplers[0].MinLOD = 0.0f; - samplers[0].MaxLOD = D3D12_FLOAT32_MAX; - samplers[0].ShaderRegister = 0; - samplers[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; - - // Sampler 1: Wrapping and linear filtering - samplers[1] = samplers[0]; - samplers[1].AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP; - samplers[1].AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP; - samplers[1].AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP; - samplers[1].ShaderRegister = 1; - - // Sampler 2: Point filtering, using SampleCmp mode to compare if sampled value >= reference value (for shadows) - samplers[2] = samplers[0]; - samplers[2].Filter = D3D12_FILTER_COMPARISON_MIN_MAG_LINEAR_MIP_POINT; - samplers[2].ComparisonFunc = D3D12_COMPARISON_FUNC_GREATER_EQUAL; - samplers[2].ShaderRegister = 2; - - D3D12_ROOT_SIGNATURE_DESC root_signature_desc = {}; - root_signature_desc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT; - root_signature_desc.NumParameters = ARRAYSIZE(params); - root_signature_desc.pParameters = params; - root_signature_desc.NumStaticSamplers = ARRAYSIZE(samplers); - root_signature_desc.pStaticSamplers = samplers; - - ComPtr signature; - ComPtr error; - FatalErrorIfFailed(D3D12SerializeRootSignature(&root_signature_desc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error)); - FatalErrorIfFailed(mDevice->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&mRootSignature))); - - // Create the command list - FatalErrorIfFailed(mDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, mCommandAllocators[mFrameIndex].Get(), nullptr, IID_PPV_ARGS(&mCommandList))); - - // Command lists are created in the recording state, but there is nothing to record yet. The main loop expects it to be closed, so close it now - FatalErrorIfFailed(mCommandList->Close()); - - // Create synchronization object - FatalErrorIfFailed(mDevice->CreateFence(mFenceValues[mFrameIndex], D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence))); - - // Increment fence value so we don't skip waiting the first time a command list is executed - mFenceValues[mFrameIndex]++; - - // Create an event handle to use for frame synchronization - mFenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); - if (mFenceEvent == nullptr) - FatalErrorIfFailed(HRESULT_FROM_WIN32(GetLastError())); - - // Initialize the queue used to upload resources to the GPU - mUploadQueue.Initialize(mDevice.Get()); - - // Create constant buffer. One per frame to avoid overwriting the constant buffer while the GPU is still using it. - for (uint n = 0; n < cFrameCount; ++n) - { - mVertexShaderConstantBufferProjection[n] = CreateConstantBuffer(sizeof(VertexShaderConstantBuffer)); - mVertexShaderConstantBufferOrtho[n] = CreateConstantBuffer(sizeof(VertexShaderConstantBuffer)); - mPixelShaderConstantBuffer[n] = CreateConstantBuffer(sizeof(PixelShaderConstantBuffer)); - } - // Store global renderer now that we're done initializing sRenderer = this; } @@ -406,52 +82,23 @@ void Renderer::OnWindowResize() { JPH_ASSERT(!mInFrame); - // Wait for the previous frame to be rendered - WaitForGpu(); - // Get new window size RECT rc; GetClientRect(mhWnd, &rc); mWindowWidth = max(rc.right - rc.left, 8); mWindowHeight = max(rc.bottom - rc.top, 8); - - // Free the render targets and views to allow resizing the swap chain - for (uint n = 0; n < cFrameCount; ++n) - { - mRTVHeap.Free(mRenderTargetViews[n]); - mRenderTargets[n].Reset(); - } - - // Resize the swap chain buffers - FatalErrorIfFailed(mSwapChain->ResizeBuffers(cFrameCount, mWindowWidth, mWindowHeight, DXGI_FORMAT_R8G8B8A8_UNORM, 0)); - - // Back buffer index may have changed after the resize (it always seems to go to 0 again) - mFrameIndex = mSwapChain->GetCurrentBackBufferIndex(); - - // Since we may have switched frame index and we know everything is done, we need to update the fence value for our other frame as completed - for (uint n = 0; n < cFrameCount; ++n) - if (mFrameIndex != n) - mFenceValues[n] = mFence->GetCompletedValue(); - - // Recreate render targets - CreateRenterTargets(); - - // Recreate depth buffer - CreateDepthBuffer(); } -static Mat44 sPerspectiveInfiniteReverseZ(float inFovY, float inAspect, float inNear) +static Mat44 sPerspectiveInfiniteReverseZ(float inFovY, float inAspect, float inNear, float inYSign) { float height = 1.0f / Tan(0.5f * inFovY); float width = height / inAspect; - return Mat44(Vec4(width, 0.0f, 0.0f, 0.0f), Vec4(0.0f, height, 0.0f, 0.0f), Vec4(0.0f, 0.0f, 0, -1.0f), Vec4(0.0f, 0.0f, inNear, 0.0f)); + return Mat44(Vec4(width, 0.0f, 0.0f, 0.0f), Vec4(0.0f, inYSign * height, 0.0f, 0.0f), Vec4(0.0f, 0.0f, 0.0f, -1.0f), Vec4(0.0f, 0.0f, inNear, 0.0f)); } void Renderer::BeginFrame(const CameraState &inCamera, float inWorldScale) { - JPH_PROFILE_FUNCTION(); - // Mark that we're in the frame JPH_ASSERT(!mInFrame); mInFrame = true; @@ -459,37 +106,6 @@ void Renderer::BeginFrame(const CameraState &inCamera, float inWorldScale) // Store state mCameraState = inCamera; - // Reset command allocator - FatalErrorIfFailed(mCommandAllocators[mFrameIndex]->Reset()); - - // Reset command list - FatalErrorIfFailed(mCommandList->Reset(mCommandAllocators[mFrameIndex].Get(), nullptr)); - - // Set root signature - mCommandList->SetGraphicsRootSignature(mRootSignature.Get()); - - // Set SRV heap - ID3D12DescriptorHeap *heaps[] = { mSRVHeap.Get() }; - mCommandList->SetDescriptorHeaps(_countof(heaps), heaps); - - // Indicate that the back buffer will be used as a render target. - D3D12_RESOURCE_BARRIER barrier; - barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; - barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; - barrier.Transition.pResource = mRenderTargets[mFrameIndex].Get(); - barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT; - barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET; - barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; - mCommandList->ResourceBarrier(1, &barrier); - - // Set the main back buffer as render target - SetRenderTarget(nullptr); - - // Clear the back buffer. - const float blue[] = { 0.098f, 0.098f, 0.439f, 1.000f }; - mCommandList->ClearRenderTargetView(mRenderTargetViews[mFrameIndex], blue, 0, nullptr); - mCommandList->ClearDepthStencilView(mDepthStencilView, D3D12_CLEAR_FLAG_DEPTH, 0.0f, 0, 0, nullptr); - // Light properties Vec3 light_pos = inWorldScale * Vec3(250, 250, 250); Vec3 light_tgt = Vec3::sZero(); @@ -499,346 +115,43 @@ void Renderer::BeginFrame(const CameraState &inCamera, float inWorldScale) float light_near = 1.0f; // Camera properties + Vec3 cam_pos = Vec3(inCamera.mPos - mBaseOffset); float camera_fovy = inCamera.mFOVY; float camera_aspect = static_cast(GetWindowWidth()) / GetWindowHeight(); float camera_fovx = 2.0f * ATan(camera_aspect * Tan(0.5f * camera_fovy)); float camera_near = 0.01f * inWorldScale; - // Set constants for vertex shader in projection mode - VertexShaderConstantBuffer *vs = mVertexShaderConstantBufferProjection[mFrameIndex]->Map(); + // Calculate camera frustum + mCameraFrustum = Frustum(cam_pos, inCamera.mForward, inCamera.mUp, camera_fovx, camera_fovy, camera_near); + + // Calculate light frustum + mLightFrustum = Frustum(light_pos, light_fwd, light_up, light_fov, light_fov, light_near); // Camera projection and view - vs->mProjection = sPerspectiveInfiniteReverseZ(camera_fovy, camera_aspect, camera_near); - Vec3 cam_pos = Vec3(inCamera.mPos - mBaseOffset); + mVSBuffer.mProjection = sPerspectiveInfiniteReverseZ(camera_fovy, camera_aspect, camera_near, mPerspectiveYSign); Vec3 tgt = cam_pos + inCamera.mForward; - vs->mView = Mat44::sLookAt(cam_pos, tgt, inCamera.mUp); + mVSBuffer.mView = Mat44::sLookAt(cam_pos, tgt, inCamera.mUp); // Light projection and view - vs->mLightProjection = sPerspectiveInfiniteReverseZ(light_fov, 1.0f, light_near); - vs->mLightView = Mat44::sLookAt(light_pos, light_tgt, light_up); - - mVertexShaderConstantBufferProjection[mFrameIndex]->Unmap(); - - // Set constants for vertex shader in ortho mode - vs = mVertexShaderConstantBufferOrtho[mFrameIndex]->Map(); + mVSBuffer.mLightProjection = sPerspectiveInfiniteReverseZ(light_fov, 1.0f, light_near, mPerspectiveYSign); + mVSBuffer.mLightView = Mat44::sLookAt(light_pos, light_tgt, light_up); // Camera ortho projection and view - vs->mProjection = Mat44(Vec4(2.0f / mWindowWidth, 0.0f, 0.0f, 0.0f), Vec4(0.0f, -2.0f / mWindowHeight, 0.0f, 0.0f), Vec4(0.0f, 0.0f, -1.0f, 0.0f), Vec4(-1.0f, 1.0f, 0.0f, 1.0f)); - vs->mView = Mat44::sIdentity(); + mVSBufferOrtho.mProjection = Mat44(Vec4(2.0f / mWindowWidth, 0.0f, 0.0f, 0.0f), Vec4(0.0f, -mPerspectiveYSign * 2.0f / mWindowHeight, 0.0f, 0.0f), Vec4(0.0f, 0.0f, -1.0f, 0.0f), Vec4(-1.0f, mPerspectiveYSign * 1.0f, 0.0f, 1.0f)); + mVSBufferOrtho.mView = Mat44::sIdentity(); // Light projection and view are unused in ortho mode - vs->mLightView = Mat44::sIdentity(); - vs->mLightProjection = Mat44::sIdentity(); - - mVertexShaderConstantBufferOrtho[mFrameIndex]->Unmap(); - - // Switch to 3d projection mode - SetProjectionMode(); + mVSBufferOrtho.mLightView = Mat44::sIdentity(); + mVSBufferOrtho.mLightProjection = Mat44::sIdentity(); // Set constants for pixel shader - PixelShaderConstantBuffer *ps = mPixelShaderConstantBuffer[mFrameIndex]->Map(); - ps->mCameraPos = Vec4(cam_pos, 0); - ps->mLightPos = Vec4(light_pos, 0); - mPixelShaderConstantBuffer[mFrameIndex]->Unmap(); - - // Set the pixel shader constant buffer data. - mPixelShaderConstantBuffer[mFrameIndex]->Bind(1); - - // Calculate camera frustum - mCameraFrustum = Frustum(cam_pos, inCamera.mForward, inCamera.mUp, camera_fovx, camera_fovy, camera_near); - - // Calculate light frustum - mLightFrustum = Frustum(light_pos, light_fwd, light_up, light_fov, light_fov, light_near); + mPSBuffer.mCameraPos = Vec4(cam_pos, 0); + mPSBuffer.mLightPos = Vec4(light_pos, 0); } void Renderer::EndFrame() { - JPH_PROFILE_FUNCTION(); - // Mark that we're no longer in the frame JPH_ASSERT(mInFrame); mInFrame = false; - - // Indicate that the back buffer will now be used to present. - D3D12_RESOURCE_BARRIER barrier; - barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; - barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; - barrier.Transition.pResource = mRenderTargets[mFrameIndex].Get(); - barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; - barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT; - barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; - mCommandList->ResourceBarrier(1, &barrier); - - // Close the command list - FatalErrorIfFailed(mCommandList->Close()); - - // Execute the command list - ID3D12CommandList* command_lists[] = { mCommandList.Get() }; - mCommandQueue->ExecuteCommandLists(_countof(command_lists), command_lists); - - // Present the frame - FatalErrorIfFailed(mSwapChain->Present(1, 0)); - - // Schedule a Signal command in the queue - UINT64 current_fence_value = mFenceValues[mFrameIndex]; - FatalErrorIfFailed(mCommandQueue->Signal(mFence.Get(), current_fence_value)); - - // Update the frame index - mFrameIndex = mSwapChain->GetCurrentBackBufferIndex(); - - // If the next frame is not ready to be rendered yet, wait until it is ready - UINT64 completed_value = mFence->GetCompletedValue(); - if (completed_value < mFenceValues[mFrameIndex]) - { - FatalErrorIfFailed(mFence->SetEventOnCompletion(mFenceValues[mFrameIndex], mFenceEvent)); - WaitForSingleObjectEx(mFenceEvent, INFINITE, FALSE); - } - - // Release all used resources - mDelayReleased[mFrameIndex].clear(); - - // Anything that's not used yet can be removed, delayed objects are now available - mResourceCache.clear(); - mDelayCached[mFrameIndex].swap(mResourceCache); - - // Set the fence value for the next frame. - mFenceValues[mFrameIndex] = current_fence_value + 1; -} - -void Renderer::SetProjectionMode() -{ - JPH_ASSERT(mInFrame); - - mVertexShaderConstantBufferProjection[mFrameIndex]->Bind(0); -} - -void Renderer::SetOrthoMode() -{ - JPH_ASSERT(mInFrame); - - mVertexShaderConstantBufferOrtho[mFrameIndex]->Bind(0); -} - -Ref Renderer::CreateTexture(const Surface *inSurface) -{ - return new Texture(this, inSurface); -} - -Ref Renderer::CreateRenderTarget(int inWidth, int inHeight) -{ - return new Texture(this, inWidth, inHeight); -} - -void Renderer::SetRenderTarget(Texture *inRenderTarget) -{ - JPH_ASSERT(mInFrame); - - // Unset the previous render target - if (mRenderTargetTexture != nullptr) - mRenderTargetTexture->SetAsRenderTarget(false); - mRenderTargetTexture = nullptr; - - if (inRenderTarget == nullptr) - { - // Set the main back buffer as render target - mCommandList->OMSetRenderTargets(1, &mRenderTargetViews[mFrameIndex], FALSE, &mDepthStencilView); - - // Set viewport - D3D12_VIEWPORT viewport = { 0.0f, 0.0f, static_cast(mWindowWidth), static_cast(mWindowHeight), 0.0f, 1.0f }; - mCommandList->RSSetViewports(1, &viewport); - - // Set scissor rect - D3D12_RECT scissor_rect = { 0, 0, static_cast(mWindowWidth), static_cast(mWindowHeight) }; - mCommandList->RSSetScissorRects(1, &scissor_rect); - } - else - { - // Use the texture as render target - inRenderTarget->SetAsRenderTarget(true); - mRenderTargetTexture = inRenderTarget; - } -} - -ComPtr Renderer::CreateVertexShader(const char *inFileName) -{ - UINT flags = D3DCOMPILE_ENABLE_STRICTNESS; -#ifdef JPH_DEBUG - flags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; -#endif - - const D3D_SHADER_MACRO defines[] = - { - { nullptr, nullptr } - }; - - // Read shader source file - Array data = ReadData(inFileName); - - // Compile source - ComPtr shader_blob, error_blob; - HRESULT hr = D3DCompile(&data[0], - (uint)data.size(), - inFileName, - defines, - D3D_COMPILE_STANDARD_FILE_INCLUDE, - "main", - "vs_5_0", - flags, - 0, - shader_blob.GetAddressOf(), - error_blob.GetAddressOf()); - if (FAILED(hr)) - { - // Throw error if compilation failed - if (error_blob) - OutputDebugStringA((const char *)error_blob->GetBufferPointer()); - FatalError("Failed to compile vertex shader"); - } - - return shader_blob; -} - -ComPtr Renderer::CreatePixelShader(const char *inFileName) -{ - UINT flags = D3DCOMPILE_ENABLE_STRICTNESS; -#ifdef JPH_DEBUG - flags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; -#endif - - const D3D_SHADER_MACRO defines[] = - { - { nullptr, nullptr } - }; - - // Read shader source file - Array data = ReadData(inFileName); - - // Compile source - ComPtr shader_blob, error_blob; - HRESULT hr = D3DCompile(&data[0], - (uint)data.size(), - inFileName, - defines, - D3D_COMPILE_STANDARD_FILE_INCLUDE, - "main", - "ps_5_0", - flags, - 0, - shader_blob.GetAddressOf(), - error_blob.GetAddressOf()); - if (FAILED(hr)) - { - // Throw error if compilation failed - if (error_blob) - OutputDebugStringA((const char *)error_blob->GetBufferPointer()); - FatalError("Failed to compile pixel shader"); - } - - return shader_blob; -} - -unique_ptr Renderer::CreateConstantBuffer(uint inBufferSize) -{ - return make_unique(this, inBufferSize); -} - -unique_ptr Renderer::CreatePipelineState(ID3DBlob *inVertexShader, const D3D12_INPUT_ELEMENT_DESC *inInputDescription, uint inInputDescriptionCount, ID3DBlob *inPixelShader, D3D12_FILL_MODE inFillMode, D3D12_PRIMITIVE_TOPOLOGY_TYPE inTopology, PipelineState::EDepthTest inDepthTest, PipelineState::EBlendMode inBlendMode, PipelineState::ECullMode inCullMode) -{ - return make_unique(this, inVertexShader, inInputDescription, inInputDescriptionCount, inPixelShader, inFillMode, inTopology, inDepthTest, inBlendMode, inCullMode); -} - -ComPtr Renderer::CreateD3DResource(D3D12_HEAP_TYPE inHeapType, D3D12_RESOURCE_STATES inResourceState, uint64 inSize) -{ - // Create a new resource - D3D12_RESOURCE_DESC desc; - desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; - desc.Alignment = 0; - desc.Width = inSize; - desc.Height = 1; - desc.DepthOrArraySize = 1; - desc.MipLevels = 1; - desc.Format = DXGI_FORMAT_UNKNOWN; - desc.SampleDesc.Count = 1; - desc.SampleDesc.Quality = 0; - desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; - desc.Flags = D3D12_RESOURCE_FLAG_NONE; - - D3D12_HEAP_PROPERTIES heap_properties = {}; - heap_properties.Type = inHeapType; - heap_properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; - heap_properties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; - heap_properties.CreationNodeMask = 1; - heap_properties.VisibleNodeMask = 1; - - ComPtr resource; - FatalErrorIfFailed(mDevice->CreateCommittedResource(&heap_properties, D3D12_HEAP_FLAG_NONE, &desc, inResourceState, nullptr, IID_PPV_ARGS(&resource))); - return resource; -} - -void Renderer::CopyD3DResource(ID3D12Resource *inDest, const void *inSrc, uint64 inSize) -{ - // Copy data to destination buffer - void *data; - D3D12_RANGE range = { 0, 0 }; // We're not going to read - FatalErrorIfFailed(inDest->Map(0, &range, &data)); - memcpy(data, inSrc, size_t(inSize)); - inDest->Unmap(0, nullptr); -} - -void Renderer::CopyD3DResource(ID3D12Resource *inDest, ID3D12Resource *inSrc, uint64 inSize) -{ - // Start a commandlist for the upload - ID3D12GraphicsCommandList *list = mUploadQueue.Start(); - - // Copy the data to the GPU - list->CopyBufferRegion(inDest, 0, inSrc, 0, inSize); - - // Change the state of the resource to generic read - D3D12_RESOURCE_BARRIER barrier; - barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; - barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; - barrier.Transition.pResource = inDest; - barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST; - barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_GENERIC_READ; - barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; - list->ResourceBarrier(1, &barrier); - - // Wait for copying to finish - mUploadQueue.ExecuteAndWait(); -} - -ComPtr Renderer::CreateD3DResourceOnDefaultHeap(const void *inData, uint64 inSize) -{ - ComPtr upload = CreateD3DResourceOnUploadHeap(inSize); - ComPtr resource = CreateD3DResource(D3D12_HEAP_TYPE_DEFAULT, D3D12_RESOURCE_STATE_COMMON, inSize); - CopyD3DResource(upload.Get(), inData, inSize); - CopyD3DResource(resource.Get(), upload.Get(), inSize); - RecycleD3DResourceOnUploadHeap(upload.Get(), inSize); - return resource; -} - -ComPtr Renderer::CreateD3DResourceOnUploadHeap(uint64 inSize) -{ - // Try cache first - ResourceCache::iterator i = mResourceCache.find(inSize); - if (i != mResourceCache.end() && !i->second.empty()) - { - ComPtr resource = i->second.back(); - i->second.pop_back(); - return resource; - } - - return CreateD3DResource(D3D12_HEAP_TYPE_UPLOAD, D3D12_RESOURCE_STATE_GENERIC_READ, inSize); -} - -void Renderer::RecycleD3DResourceOnUploadHeap(ID3D12Resource *inResource, uint64 inSize) -{ - if (!mIsExiting) - mDelayCached[mFrameIndex][inSize].push_back(inResource); -} - -void Renderer::RecycleD3DObject(ID3D12Object *inResource) -{ - if (!mIsExiting) - mDelayReleased[mFrameIndex].push_back(inResource); } diff --git a/TestFramework/Renderer/Renderer.h b/TestFramework/Renderer/Renderer.h index 31260a64d..517915913 100644 --- a/TestFramework/Renderer/Renderer.h +++ b/TestFramework/Renderer/Renderer.h @@ -4,13 +4,13 @@ #pragma once -#include #include #include -#include #include -#include -#include +#include +#include +#include +#include #include // Forward declares @@ -32,13 +32,10 @@ class Renderer { public: /// Destructor - ~Renderer(); + virtual ~Renderer() = default; /// Initialize DirectX - void Initialize(); - - /// Callback when the window resizes and the back buffer needs to be adjusted - void OnWindowResize(); + virtual void Initialize(); /// Get window size int GetWindowWidth() { return mWindowWidth; } @@ -47,42 +44,35 @@ class Renderer /// Access to the window handle HWND GetWindowHandle() const { return mhWnd; } - /// Access to the most important DirectX structures - ID3D12Device * GetDevice() { return mDevice.Get(); } - ID3D12RootSignature * GetRootSignature() { return mRootSignature.Get(); } - ID3D12GraphicsCommandList * GetCommandList() { JPH_ASSERT(mInFrame); return mCommandList.Get(); } - CommandQueue & GetUploadQueue() { return mUploadQueue; } - DescriptorHeap & GetDSVHeap() { return mDSVHeap; } - DescriptorHeap & GetSRVHeap() { return mSRVHeap; } - /// Start / end drawing a frame - void BeginFrame(const CameraState &inCamera, float inWorldScale); - void EndFrame(); + virtual void BeginFrame(const CameraState &inCamera, float inWorldScale); + virtual void EndShadowPass() = 0; + virtual void EndFrame(); /// Switch between orthographic and 3D projection mode - void SetProjectionMode(); - void SetOrthoMode(); + virtual void SetProjectionMode() = 0; + virtual void SetOrthoMode() = 0; /// Create texture from an image surface - Ref CreateTexture(const Surface *inSurface); - - /// Create a texture to render to (currently depth buffer only) - Ref CreateRenderTarget(int inWidth, int inHeight); - - /// Change the render target to a texture. Use nullptr to set back to the main render target. - void SetRenderTarget(Texture *inRenderTarget); + virtual Ref CreateTexture(const Surface *inSurface) = 0; /// Compile a vertex shader - ComPtr CreateVertexShader(const char *inFileName); + virtual Ref CreateVertexShader(const char *inFileName) = 0; /// Compile a pixel shader - ComPtr CreatePixelShader(const char *inFileName); - - /// Create a constant buffer for the shader - unique_ptr CreateConstantBuffer(uint inBufferSize); + virtual Ref CreatePixelShader(const char *inFileName) = 0; /// Create pipeline state object that defines the complete state of how primitives should be rendered - unique_ptr CreatePipelineState(ID3DBlob *inVertexShader, const D3D12_INPUT_ELEMENT_DESC *inInputDescription, uint inInputDescriptionCount, ID3DBlob *inPixelShader, D3D12_FILL_MODE inFillMode, D3D12_PRIMITIVE_TOPOLOGY_TYPE inTopology, PipelineState::EDepthTest inDepthTest, PipelineState::EBlendMode inBlendMode, PipelineState::ECullMode inCullMode); + virtual unique_ptr CreatePipelineState(const VertexShader *inVertexShader, const PipelineState::EInputDescription *inInputDescription, uint inInputDescriptionCount, const PixelShader *inPixelShader, PipelineState::EDrawPass inDrawPass, PipelineState::EFillMode inFillMode, PipelineState::ETopology inTopology, PipelineState::EDepthTest inDepthTest, PipelineState::EBlendMode inBlendMode, PipelineState::ECullMode inCullMode) = 0; + + /// Create a render primitive + virtual RenderPrimitive * CreateRenderPrimitive(PipelineState::ETopology inType) = 0; + + /// Create render instances object to allow drawing batches of objects + virtual RenderInstances * CreateRenderInstances() = 0; + + /// Get the shadow map texture + virtual Texture * GetShadowMap() const = 0; /// Get the camera state / frustum (only valid between BeginFrame() / EndFrame()) const CameraState & GetCameraState() const { JPH_ASSERT(mInFrame); return mCameraState; } @@ -98,80 +88,41 @@ class Renderer /// How many frames our pipeline is static const uint cFrameCount = 2; + /// Size of the shadow map will be cShadowMapSize x cShadowMapSize pixels + static const uint cShadowMapSize = 4096; + /// Which frame is currently rendering (to keep track of which buffers are free to overwrite) uint GetCurrentFrameIndex() const { JPH_ASSERT(mInFrame); return mFrameIndex; } - /// Create a buffer on the default heap (usable for permanent buffers) - ComPtr CreateD3DResourceOnDefaultHeap(const void *inData, uint64 inSize); - - /// Create buffer on the upload heap (usable for temporary buffers). - ComPtr CreateD3DResourceOnUploadHeap(uint64 inSize); - - /// Recycle a buffer on the upload heap. This puts it back in a cache and will reuse it when it is certain the GPU is no longer referencing it. - void RecycleD3DResourceOnUploadHeap(ID3D12Resource *inResource, uint64 inSize); - - /// Keeps a reference to the resource until the current frame has finished - void RecycleD3DObject(ID3D12Object *inResource); - -private: - // Wait for pending GPU work to complete - void WaitForGpu(); - - // Create render targets and their views - void CreateRenterTargets(); - - // Create a depth buffer for the back buffer - void CreateDepthBuffer(); - - // Function to create a ID3D12Resource on specified heap with specified state - ComPtr CreateD3DResource(D3D12_HEAP_TYPE inHeapType, D3D12_RESOURCE_STATES inResourceState, uint64 inSize); - - // Copy CPU memory into a ID3D12Resource - void CopyD3DResource(ID3D12Resource *inDest, const void *inSrc, uint64 inSize); - - // Copy a CPU resource to a GPU resource - void CopyD3DResource(ID3D12Resource *inDest, ID3D12Resource *inSrc, uint64 inSize); + /// Callback when the window resizes and the back buffer needs to be adjusted + virtual void OnWindowResize(); + +protected: + struct VertexShaderConstantBuffer + { + Mat44 mView; + Mat44 mProjection; + Mat44 mLightView; + Mat44 mLightProjection; + }; + + struct PixelShaderConstantBuffer + { + Vec4 mCameraPos; + Vec4 mLightPos; + }; HWND mhWnd; int mWindowWidth = 1920; int mWindowHeight = 1080; - unique_ptr mVertexShaderConstantBufferProjection[cFrameCount]; - unique_ptr mVertexShaderConstantBufferOrtho[cFrameCount]; - unique_ptr mPixelShaderConstantBuffer[cFrameCount]; + float mPerspectiveYSign = 1.0f; ///< Sign for the Y coordinate in the projection matrix (1 for DX, -1 for Vulkan) bool mInFrame = false; ///< If we're within a BeginFrame() / EndFrame() pair CameraState mCameraState; RVec3 mBaseOffset { RVec3::sZero() }; ///< Offset to subtract from the camera position to deal with large worlds Frustum mCameraFrustum; Frustum mLightFrustum; - - // DirectX interfaces - ComPtr mDXGIFactory; - ComPtr mDevice; - DescriptorHeap mRTVHeap; ///< Render target view heap - DescriptorHeap mDSVHeap; ///< Depth stencil view heap - DescriptorHeap mSRVHeap; ///< Shader resource view heap - ComPtr mSwapChain; - ComPtr mRenderTargets[cFrameCount]; ///< Two render targets (we're double buffering in order for the CPU to continue while the GPU is rendering) - D3D12_CPU_DESCRIPTOR_HANDLE mRenderTargetViews[cFrameCount]; ///< The two render views corresponding to the render targets - ComPtr mDepthStencilBuffer; ///< The main depth buffer - D3D12_CPU_DESCRIPTOR_HANDLE mDepthStencilView { 0 }; ///< A view for binding the depth buffer - ComPtr mCommandAllocators[cFrameCount]; ///< Two command allocator lists (one per frame) - ComPtr mCommandQueue; ///< The command queue that will execute commands (there's only 1 since we want to finish rendering 1 frame before moving onto the next) - ComPtr mCommandList; ///< The command list - ComPtr mRootSignature; ///< The root signature, we have a simple application so we only need 1, which is suitable for all our shaders - Ref mRenderTargetTexture; ///< When rendering to a texture, this is the active texture - CommandQueue mUploadQueue; ///< Queue used to upload resources to GPU memory - - // Synchronization objects used to finish rendering and swapping before reusing a command queue - uint mFrameIndex; ///< Current frame index (0 or 1) - HANDLE mFenceEvent; ///< Fence event to wait for the previous frame rendering to complete (in order to free 1 of the buffers) - ComPtr mFence; ///< Fence object, used to signal the end of a frame - UINT64 mFenceValues[cFrameCount] = {}; ///< Values that were used to signal completion of one of the two frames - - using ResourceCache = UnorderedMap>>; - - ResourceCache mResourceCache; ///< Cache items ready to be reused - ResourceCache mDelayCached[cFrameCount]; ///< List of reusable ID3D12Resources that are potentially referenced by the GPU so can be used only when the GPU finishes - Array> mDelayReleased[cFrameCount]; ///< Objects that are potentially referenced by the GPU so can only be freed when the GPU finishes - bool mIsExiting = false; ///< When exiting we don't want to add references too buffers + uint mFrameIndex = 0; ///< Current frame index (0 or 1) + VertexShaderConstantBuffer mVSBuffer; + VertexShaderConstantBuffer mVSBufferOrtho; + PixelShaderConstantBuffer mPSBuffer; }; diff --git a/TestFramework/Renderer/Texture.h b/TestFramework/Renderer/Texture.h index c3973b455..47c6f769c 100644 --- a/TestFramework/Renderer/Texture.h +++ b/TestFramework/Renderer/Texture.h @@ -7,38 +7,23 @@ #include /// Forward declares -class Renderer; class Surface; class Texture : public RefTarget { public: - /// Constructor, called by Renderer::CreateTexture - Texture(Renderer *inRenderer, const Surface *inSurface); // Create a normal texture - Texture(Renderer *inRenderer, int inWidth, int inHeight); // Create a render target (depth only) - ~Texture(); + /// Constructor + Texture(int inWidth, int inHeight) : mWidth(inWidth), mHeight(inHeight) { } + virtual ~Texture() = default; /// Get dimensions of texture inline int GetWidth() const { return mWidth; } inline int GetHeight() const { return mHeight; } /// Bind texture to the pixel shader - void Bind(int inSlot) const; - - /// Clear this texture (only possible for render targets) - void ClearRenderTarget(); - - /// Activate this texture as the current render target, called by Renderer::SetRenderTarget - void SetAsRenderTarget(bool inSet) const; - -private: - Renderer * mRenderer; + virtual void Bind() const = 0; +protected: int mWidth; int mHeight; - - ComPtr mTexture; ///< The texture data - - D3D12_CPU_DESCRIPTOR_HANDLE mSRV { 0 }; ///< Shader resource view to bind as texture - D3D12_CPU_DESCRIPTOR_HANDLE mDSV { 0 }; ///< Depth shader view to bind as render target }; diff --git a/TestFramework/Renderer/VK/BufferVK.h b/TestFramework/Renderer/VK/BufferVK.h new file mode 100644 index 000000000..5dc96d6b0 --- /dev/null +++ b/TestFramework/Renderer/VK/BufferVK.h @@ -0,0 +1,35 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +/// Simple wrapper class to manage a Vulkan buffer +class BufferVK +{ +public: + /// Free memory associated with a buffer + void Free(VkDevice inDevice) + { + if (mBuffer != VK_NULL_HANDLE) + { + vkDestroyBuffer(inDevice, mBuffer, nullptr); + mBuffer = VK_NULL_HANDLE; + } + + if (mMemory != VK_NULL_HANDLE) + { + vkFreeMemory(inDevice, mMemory, nullptr); + mMemory = VK_NULL_HANDLE; + } + } + + VkBuffer mBuffer = VK_NULL_HANDLE; + VkDeviceMemory mMemory = VK_NULL_HANDLE; + + VkBufferUsageFlags mUsage; + VkMemoryPropertyFlags mProperties; + VkDeviceSize mSize = 0; +}; diff --git a/TestFramework/Renderer/VK/ConstantBufferVK.cpp b/TestFramework/Renderer/VK/ConstantBufferVK.cpp new file mode 100644 index 000000000..66207dd02 --- /dev/null +++ b/TestFramework/Renderer/VK/ConstantBufferVK.cpp @@ -0,0 +1,32 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#include + +#include +#include +#include + +ConstantBufferVK::ConstantBufferVK(RendererVK *inRenderer, VkDeviceSize inBufferSize) : + mRenderer(inRenderer) +{ + mRenderer->CreateBuffer(inBufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, mBuffer); +} + +ConstantBufferVK::~ConstantBufferVK() +{ + mRenderer->FreeBuffer(mBuffer); +} + +void *ConstantBufferVK::MapInternal() +{ + void *data = nullptr; + FatalErrorIfFailed(vkMapMemory(mRenderer->GetDevice(), mBuffer.mMemory, 0, mBuffer.mSize, 0, &data)); + return data; +} + +void ConstantBufferVK::Unmap() +{ + vkUnmapMemory(mRenderer->GetDevice(), mBuffer.mMemory); +} diff --git a/TestFramework/Renderer/VK/ConstantBufferVK.h b/TestFramework/Renderer/VK/ConstantBufferVK.h new file mode 100644 index 000000000..0b6775b8b --- /dev/null +++ b/TestFramework/Renderer/VK/ConstantBufferVK.h @@ -0,0 +1,30 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +class RendererVK; + +/// A binary blob that can be used to pass constants to a shader +class ConstantBufferVK +{ +public: + /// Constructor + ConstantBufferVK(RendererVK *inRenderer, VkDeviceSize inBufferSize); + ~ConstantBufferVK(); + + /// Map / unmap buffer (get pointer to data). This will discard all data in the buffer. + template T * Map() { return reinterpret_cast(MapInternal()); } + void Unmap(); + + VkBuffer GetBuffer() const { return mBuffer.mBuffer; } + +private: + void * MapInternal(); + + RendererVK * mRenderer; + BufferVK mBuffer; +}; diff --git a/TestFramework/Renderer/VK/FatalErrorIfFailedVK.cpp b/TestFramework/Renderer/VK/FatalErrorIfFailedVK.cpp new file mode 100644 index 000000000..84d46f3aa --- /dev/null +++ b/TestFramework/Renderer/VK/FatalErrorIfFailedVK.cpp @@ -0,0 +1,14 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#include + +#include +#include + +void FatalErrorIfFailed(VkResult inVkResult) +{ + if (inVkResult != VK_SUCCESS) + FatalError("Vulkan error returned: %d", inVkResult); +} diff --git a/TestFramework/Renderer/VK/FatalErrorIfFailedVK.h b/TestFramework/Renderer/VK/FatalErrorIfFailedVK.h new file mode 100644 index 000000000..6720dbdf9 --- /dev/null +++ b/TestFramework/Renderer/VK/FatalErrorIfFailedVK.h @@ -0,0 +1,11 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +/// Convert Vulkan error to readable text and alert +void FatalErrorIfFailed(VkResult inVkResult); + diff --git a/TestFramework/Renderer/VK/PipelineStateVK.cpp b/TestFramework/Renderer/VK/PipelineStateVK.cpp new file mode 100644 index 000000000..1f62e4e26 --- /dev/null +++ b/TestFramework/Renderer/VK/PipelineStateVK.cpp @@ -0,0 +1,189 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#include + +#include +#include +#include + +PipelineStateVK::PipelineStateVK(RendererVK *inRenderer, const VertexShaderVK *inVertexShader, const EInputDescription *inInputDescription, uint inInputDescriptionCount, const PixelShaderVK *inPixelShader, EDrawPass inDrawPass, EFillMode inFillMode, ETopology inTopology, EDepthTest inDepthTest, EBlendMode inBlendMode, ECullMode inCullMode) : + mRenderer(inRenderer), + mVertexShader(inVertexShader), + mPixelShader(inPixelShader) +{ + VkPipelineShaderStageCreateInfo shader_stages[] = { inVertexShader->mStageInfo, inPixelShader->mStageInfo }; + + // TODO: This doesn't follow the SPIR-V alignment rules + Array attribute_descriptions; + VkVertexInputAttributeDescription temp_vtx = { }, temp_instance = { }; + temp_instance.binding = 1; + uint instance_alignment = 1; + for (uint i = 0; i < inInputDescriptionCount; ++i) + switch (inInputDescription[i]) + { + case EInputDescription::Position: + temp_vtx.format = VK_FORMAT_R32G32B32_SFLOAT; + attribute_descriptions.push_back(temp_vtx); + temp_vtx.offset += 3 * sizeof(float); + break; + + case EInputDescription::Color: + temp_vtx.format = VK_FORMAT_R8G8B8A8_UNORM; + attribute_descriptions.push_back(temp_vtx); + temp_vtx.offset += 4 * sizeof(uint8); + break; + + case EInputDescription::Normal: + temp_vtx.format = VK_FORMAT_R32G32B32_SFLOAT; + attribute_descriptions.push_back(temp_vtx); + temp_vtx.offset += 3 * sizeof(float); + break; + + case EInputDescription::TexCoord: + temp_vtx.format = VK_FORMAT_R32G32_SFLOAT; + attribute_descriptions.push_back(temp_vtx); + temp_vtx.offset += 2 * sizeof(float); + break; + + case EInputDescription::InstanceColor: + instance_alignment = max(instance_alignment, 4u); + temp_instance.format = VK_FORMAT_R8G8B8A8_UNORM; + attribute_descriptions.push_back(temp_instance); + temp_instance.offset += 4 * sizeof(uint8); + break; + + case EInputDescription::InstanceTransform: + instance_alignment = max(instance_alignment, 16u); + temp_instance.format = VK_FORMAT_R32G32B32A32_SFLOAT; + for (int j = 0; j < 4; ++j) + { + attribute_descriptions.push_back(temp_instance); + temp_instance.offset += 4 * sizeof(float); + } + break; + + case EInputDescription::InstanceInvTransform: + instance_alignment = max(instance_alignment, 16u); + temp_instance.format = VK_FORMAT_R32G32B32A32_SFLOAT; + for (int j = 0; j < 4; ++j) + { + attribute_descriptions.push_back(temp_instance); + temp_instance.offset += 4 * sizeof(float); + } + break; + } + + for (uint32 i = 0; i < uint32(attribute_descriptions.size()); ++i) + attribute_descriptions[i].location = i; + + VkVertexInputBindingDescription binding_description[2]; + binding_description[0].binding = 0; + binding_description[0].stride = temp_vtx.offset; + binding_description[0].inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + binding_description[1].binding = 1; + binding_description[1].stride = AlignUp(temp_instance.offset, instance_alignment); + binding_description[1].inputRate = VK_VERTEX_INPUT_RATE_INSTANCE; + + VkPipelineVertexInputStateCreateInfo vertex_input_info = {}; + vertex_input_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertex_input_info.vertexBindingDescriptionCount = temp_instance.offset > 0? 2 : 1; + vertex_input_info.pVertexBindingDescriptions = binding_description; + vertex_input_info.vertexAttributeDescriptionCount = uint32(attribute_descriptions.size()); + vertex_input_info.pVertexAttributeDescriptions = attribute_descriptions.data(); + + VkPipelineInputAssemblyStateCreateInfo input_assembly = {}; + input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + input_assembly.topology = inTopology == ETopology::Triangle? VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST : VK_PRIMITIVE_TOPOLOGY_LINE_LIST; + input_assembly.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo viewport_state = {}; + viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewport_state.viewportCount = 1; + viewport_state.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterizer = {}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = inFillMode == EFillMode::Solid? VK_POLYGON_MODE_FILL : VK_POLYGON_MODE_LINE; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = inCullMode == ECullMode::Backface? VK_CULL_MODE_BACK_BIT : VK_CULL_MODE_FRONT_BIT; + rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling = {}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineDepthStencilStateCreateInfo depth_stencil = {}; + depth_stencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + depth_stencil.depthTestEnable = inDepthTest == EDepthTest::On? VK_TRUE : VK_FALSE; + depth_stencil.depthWriteEnable = inDepthTest == EDepthTest::On? VK_TRUE : VK_FALSE; + depth_stencil.depthCompareOp = VK_COMPARE_OP_GREATER; // Reverse-Z, greater is closer + + VkPipelineColorBlendAttachmentState color_blend_attachment = {}; + color_blend_attachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + switch (inBlendMode) + { + case EBlendMode::Write: + color_blend_attachment.blendEnable = VK_FALSE; + break; + + case EBlendMode::AlphaBlend: + color_blend_attachment.blendEnable = VK_TRUE; + color_blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; + color_blend_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + color_blend_attachment.colorBlendOp = VK_BLEND_OP_ADD; + color_blend_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + color_blend_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + color_blend_attachment.alphaBlendOp = VK_BLEND_OP_ADD; + break; + } + + VkPipelineColorBlendStateCreateInfo color_blending = {}; + color_blending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + color_blending.logicOpEnable = VK_FALSE; + color_blending.logicOp = VK_LOGIC_OP_COPY; + color_blending.attachmentCount = 1; + color_blending.pAttachments = &color_blend_attachment; + + VkDynamicState dynamic_states[] = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamic_state = {}; + dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamic_state.dynamicStateCount = std::size(dynamic_states); + dynamic_state.pDynamicStates = dynamic_states; + + VkGraphicsPipelineCreateInfo pipeline_info = {}; + pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipeline_info.stageCount = std::size(shader_stages); + pipeline_info.pStages = shader_stages; + pipeline_info.pVertexInputState = &vertex_input_info; + pipeline_info.pInputAssemblyState = &input_assembly; + pipeline_info.pViewportState = &viewport_state; + pipeline_info.pRasterizationState = &rasterizer; + pipeline_info.pMultisampleState = &multisampling; + pipeline_info.pDepthStencilState = &depth_stencil; + pipeline_info.pColorBlendState = &color_blending; + pipeline_info.pDynamicState = &dynamic_state; + pipeline_info.layout = mRenderer->GetPipelineLayout(); + pipeline_info.renderPass = inDrawPass == EDrawPass::Normal? mRenderer->GetRenderPass() : mRenderer->GetRenderPassShadow(); + FatalErrorIfFailed(vkCreateGraphicsPipelines(mRenderer->GetDevice(), VK_NULL_HANDLE, 1, &pipeline_info, nullptr, &mGraphicsPipeline)); +} + +PipelineStateVK::~PipelineStateVK() +{ + vkDeviceWaitIdle(mRenderer->GetDevice()); + + vkDestroyPipeline(mRenderer->GetDevice(), mGraphicsPipeline, nullptr); +} + +void PipelineStateVK::Activate() +{ + vkCmdBindPipeline(mRenderer->GetCommandBuffer(), VK_PIPELINE_BIND_POINT_GRAPHICS, mGraphicsPipeline); +} diff --git a/TestFramework/Renderer/VK/PipelineStateVK.h b/TestFramework/Renderer/VK/PipelineStateVK.h new file mode 100644 index 000000000..753110e6e --- /dev/null +++ b/TestFramework/Renderer/VK/PipelineStateVK.h @@ -0,0 +1,30 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include +#include + +class RendererVK; + +/// Vulkan pipeline state object +class PipelineStateVK : public PipelineState +{ +public: + /// Constructor + PipelineStateVK(RendererVK *inRenderer, const VertexShaderVK *inVertexShader, const EInputDescription *inInputDescription, uint inInputDescriptionCount, const PixelShaderVK *inPixelShader, EDrawPass inDrawPass, EFillMode inFillMode, ETopology inTopology, EDepthTest inDepthTest, EBlendMode inBlendMode, ECullMode inCullMode); + virtual ~PipelineStateVK() override; + + /// Make this pipeline state active (any primitives rendered after this will use this state) + virtual void Activate() override; + +private: + RendererVK * mRenderer; + RefConst mVertexShader; + RefConst mPixelShader; + + VkPipeline mGraphicsPipeline; +}; diff --git a/TestFramework/Renderer/VK/PixelShaderVK.h b/TestFramework/Renderer/VK/PixelShaderVK.h new file mode 100644 index 000000000..08b1aad3e --- /dev/null +++ b/TestFramework/Renderer/VK/PixelShaderVK.h @@ -0,0 +1,34 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +#include + +/// Pixel shader handle for Vulkan +class PixelShaderVK : public PixelShader +{ +public: + /// Constructor + PixelShaderVK(VkDevice inDevice, VkShaderModule inShaderModule) : + mDevice(inDevice), + mStageInfo() + { + mStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + mStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + mStageInfo.module = inShaderModule; + mStageInfo.pName = "main"; + } + + /// Destructor + virtual ~PixelShaderVK() override + { + vkDestroyShaderModule(mDevice, mStageInfo.module, nullptr); + } + + VkDevice mDevice; + VkPipelineShaderStageCreateInfo mStageInfo; +}; diff --git a/TestFramework/Renderer/VK/RenderInstancesVK.cpp b/TestFramework/Renderer/VK/RenderInstancesVK.cpp new file mode 100644 index 000000000..00dd60c71 --- /dev/null +++ b/TestFramework/Renderer/VK/RenderInstancesVK.cpp @@ -0,0 +1,54 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#include + +#include +#include +#include + +void RenderInstancesVK::Clear() +{ + mRenderer->FreeBuffer(mInstancesBuffer); +} + +void RenderInstancesVK::CreateBuffer(int inNumInstances, int inInstanceSize) +{ + Clear(); + + mRenderer->CreateBuffer(VkDeviceSize(inNumInstances) * inInstanceSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, mInstancesBuffer); +} + +void *RenderInstancesVK::Lock() +{ + void *data; + FatalErrorIfFailed(vkMapMemory(mRenderer->GetDevice(), mInstancesBuffer.mMemory, 0, mInstancesBuffer.mSize, 0, &data)); + return data; +} + +void RenderInstancesVK::Unlock() +{ + vkUnmapMemory(mRenderer->GetDevice(), mInstancesBuffer.mMemory); +} + +void RenderInstancesVK::Draw(RenderPrimitive *inPrimitive, int inStartInstance, int inNumInstances) const +{ + VkCommandBuffer command_buffer = mRenderer->GetCommandBuffer(); + RenderPrimitiveVK *primitive = static_cast(inPrimitive); + + VkBuffer buffers[] = { primitive->mVertexBuffer.mBuffer, mInstancesBuffer.mBuffer }; + VkDeviceSize offsets[] = { 0, 0 }; + vkCmdBindVertexBuffers(command_buffer, 0, 2, buffers, offsets); + + if (primitive->mIndexBuffer.mBuffer == VK_NULL_HANDLE) + { + vkCmdDraw(command_buffer, primitive->mNumVtxToDraw, inNumInstances, 0, inStartInstance); + } + else + { + vkCmdBindIndexBuffer(command_buffer, primitive->mIndexBuffer.mBuffer, 0, VK_INDEX_TYPE_UINT32); + + vkCmdDrawIndexed(command_buffer, primitive->mNumIdxToDraw, inNumInstances, 0, 0, inStartInstance); + } +} diff --git a/TestFramework/Renderer/VK/RenderInstancesVK.h b/TestFramework/Renderer/VK/RenderInstancesVK.h new file mode 100644 index 000000000..6ac0d87e9 --- /dev/null +++ b/TestFramework/Renderer/VK/RenderInstancesVK.h @@ -0,0 +1,35 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include + +class RenderPrimitive; + +/// Vulkan implementation of a render instances object +class RenderInstancesVK : public RenderInstances +{ +public: + /// Constructor + RenderInstancesVK(RendererVK *inRenderer) : mRenderer(inRenderer) { } + virtual ~RenderInstancesVK() override { Clear(); } + + /// Erase all instance data + virtual void Clear() override; + + /// Instance buffer management functions + virtual void CreateBuffer(int inNumInstances, int inInstanceSize) override; + virtual void * Lock() override; + virtual void Unlock() override; + + /// Draw the instances when context has been set by Renderer::BindShader + virtual void Draw(RenderPrimitive *inPrimitive, int inStartInstance, int inNumInstances) const override; + +private: + RendererVK * mRenderer; + + BufferVK mInstancesBuffer; +}; diff --git a/TestFramework/Renderer/VK/RenderPrimitiveVK.cpp b/TestFramework/Renderer/VK/RenderPrimitiveVK.cpp new file mode 100644 index 000000000..ca95647cf --- /dev/null +++ b/TestFramework/Renderer/VK/RenderPrimitiveVK.cpp @@ -0,0 +1,100 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#include + +#include +#include + +void RenderPrimitiveVK::ReleaseVertexBuffer() +{ + mRenderer->FreeBuffer(mVertexBuffer); + mVertexBufferDeviceLocal = false; + + RenderPrimitive::ReleaseVertexBuffer(); +} + +void RenderPrimitiveVK::ReleaseIndexBuffer() +{ + mRenderer->FreeBuffer(mIndexBuffer); + mIndexBufferDeviceLocal = false; + + RenderPrimitive::ReleaseIndexBuffer(); +} + +void RenderPrimitiveVK::CreateVertexBuffer(int inNumVtx, int inVtxSize, const void *inData) +{ + RenderPrimitive::CreateVertexBuffer(inNumVtx, inVtxSize, inData); + + VkDeviceSize size = VkDeviceSize(inNumVtx) * inVtxSize; + if (inData != nullptr) + { + mRenderer->CreateDeviceLocalBuffer(inData, size, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, mVertexBuffer); + mVertexBufferDeviceLocal = true; + } + else + mRenderer->CreateBuffer(size, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, mVertexBuffer); +} + +void *RenderPrimitiveVK::LockVertexBuffer() +{ + JPH_ASSERT(!mVertexBufferDeviceLocal); + + void *data; + FatalErrorIfFailed(vkMapMemory(mRenderer->GetDevice(), mVertexBuffer.mMemory, 0, VkDeviceSize(mNumVtx) * mVtxSize, 0, &data)); + return data; +} + +void RenderPrimitiveVK::UnlockVertexBuffer() +{ + vkUnmapMemory(mRenderer->GetDevice(), mVertexBuffer.mMemory); +} + +void RenderPrimitiveVK::CreateIndexBuffer(int inNumIdx, const uint32 *inData) +{ + RenderPrimitive::CreateIndexBuffer(inNumIdx, inData); + + VkDeviceSize size = VkDeviceSize(inNumIdx) * sizeof(uint32); + if (inData != nullptr) + { + mRenderer->CreateDeviceLocalBuffer(inData, size, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, mIndexBuffer); + mIndexBufferDeviceLocal = true; + } + else + mRenderer->CreateBuffer(size, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, mIndexBuffer); +} + +uint32 *RenderPrimitiveVK::LockIndexBuffer() +{ + JPH_ASSERT(!mIndexBufferDeviceLocal); + + void *data; + vkMapMemory(mRenderer->GetDevice(), mIndexBuffer.mMemory, 0, VkDeviceSize(mNumIdx) * sizeof(uint32), 0, &data); + return reinterpret_cast(data); +} + +void RenderPrimitiveVK::UnlockIndexBuffer() +{ + vkUnmapMemory(mRenderer->GetDevice(), mIndexBuffer.mMemory); +} + +void RenderPrimitiveVK::Draw() const +{ + VkCommandBuffer command_buffer = mRenderer->GetCommandBuffer(); + + VkBuffer vertex_buffers[] = { mVertexBuffer.mBuffer }; + VkDeviceSize offsets[] = { 0 }; + vkCmdBindVertexBuffers(command_buffer, 0, 1, vertex_buffers, offsets); + + if (mIndexBuffer.mBuffer == VK_NULL_HANDLE) + { + vkCmdDraw(command_buffer, mNumVtxToDraw, 1, 0, 0); + } + else + { + vkCmdBindIndexBuffer(command_buffer, mIndexBuffer.mBuffer, 0, VK_INDEX_TYPE_UINT32); + + vkCmdDrawIndexed(command_buffer, mNumIdxToDraw, 1, 0, 0, 0); + } +} diff --git a/TestFramework/Renderer/VK/RenderPrimitiveVK.h b/TestFramework/Renderer/VK/RenderPrimitiveVK.h new file mode 100644 index 000000000..9c666f390 --- /dev/null +++ b/TestFramework/Renderer/VK/RenderPrimitiveVK.h @@ -0,0 +1,44 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include +#include + +/// Vulkan implementation of a render primitive +class RenderPrimitiveVK : public RenderPrimitive +{ +public: + /// Constructor + RenderPrimitiveVK(RendererVK *inRenderer) : mRenderer(inRenderer) { } + virtual ~RenderPrimitiveVK() override { Clear(); } + + /// Vertex buffer management functions + virtual void CreateVertexBuffer(int inNumVtx, int inVtxSize, const void *inData = nullptr) override; + virtual void ReleaseVertexBuffer() override; + virtual void * LockVertexBuffer() override; + virtual void UnlockVertexBuffer() override; + + /// Index buffer management functions + virtual void CreateIndexBuffer(int inNumIdx, const uint32 *inData = nullptr) override; + virtual void ReleaseIndexBuffer() override; + virtual uint32 * LockIndexBuffer() override; + virtual void UnlockIndexBuffer() override; + + /// Draw the primitive + virtual void Draw() const override; + +private: + friend class RenderInstancesVK; + + RendererVK * mRenderer; + + BufferVK mVertexBuffer; + bool mVertexBufferDeviceLocal = false; + + BufferVK mIndexBuffer; + bool mIndexBufferDeviceLocal = false; +}; diff --git a/TestFramework/Renderer/VK/RendererVK.cpp b/TestFramework/Renderer/VK/RendererVK.cpp new file mode 100644 index 000000000..2720de73b --- /dev/null +++ b/TestFramework/Renderer/VK/RendererVK.cpp @@ -0,0 +1,1112 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef JPH_DEBUG + +static VKAPI_ATTR VkBool32 VKAPI_CALL sVulkanDebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT inSeverity, [[maybe_unused]] VkDebugUtilsMessageTypeFlagsEXT inType, const VkDebugUtilsMessengerCallbackDataEXT *inCallbackData, [[maybe_unused]] void *inUserData) +{ + Trace("VK: %s", inCallbackData->pMessage); + JPH_ASSERT((inSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) == 0); + return VK_FALSE; +} + +#endif // JPH_DEBUG + +RendererVK::~RendererVK() +{ + vkDeviceWaitIdle(mDevice); + + // Destroy the shadow map + mShadowMap = nullptr; + vkDestroyFramebuffer(mDevice, mShadowFrameBuffer, nullptr); + + // Release constant buffers + for (unique_ptr &cb : mVertexShaderConstantBufferProjection) + cb = nullptr; + for (unique_ptr &cb : mVertexShaderConstantBufferOrtho) + cb = nullptr; + for (unique_ptr &cb : mPixelShaderConstantBuffer) + cb = nullptr; + + // Free all buffers + for (BufferCache &bc : mFreedBuffers) + for (BufferCache::value_type &vt : bc) + for (BufferVK &bvk : vt.second) + bvk.Free(mDevice); + for (BufferCache::value_type &vt : mBufferCache) + for (BufferVK &bvk : vt.second) + bvk.Free(mDevice); + + for (VkFence fence : mInFlightFences) + vkDestroyFence(mDevice, fence, nullptr); + + for (VkSemaphore semaphore : mRenderFinishedSemaphores) + vkDestroySemaphore(mDevice, semaphore, nullptr); + for (VkSemaphore semaphore : mImageAvailableSemaphores) + vkDestroySemaphore(mDevice, semaphore, nullptr); + + vkDestroyCommandPool(mDevice, mCommandPool, nullptr); + + vkDestroyPipelineLayout(mDevice, mPipelineLayout, nullptr); + + vkDestroyRenderPass(mDevice, mRenderPassShadow, nullptr); + vkDestroyRenderPass(mDevice, mRenderPass, nullptr); + + vkDestroyDescriptorPool(mDevice, mDescriptorPool, nullptr); + + vkDestroySampler(mDevice, mTextureSamplerShadow, nullptr); + vkDestroySampler(mDevice, mTextureSamplerRepeat, nullptr); + + vkDestroyDescriptorSetLayout(mDevice, mDescriptorSetLayoutUBO, nullptr); + vkDestroyDescriptorSetLayout(mDevice, mDescriptorSetLayoutTexture, nullptr); + + DestroySwapChain(); + + vkDestroySurfaceKHR(mInstance, mSurface, nullptr); + + vkDestroyDevice(mDevice, nullptr); + +#ifdef _DEBUG + PFN_vkDestroyDebugUtilsMessengerEXT vkDestroyDebugUtilsMessengerEXT = (PFN_vkDestroyDebugUtilsMessengerEXT)(void *)vkGetInstanceProcAddr(mInstance, "vkDestroyDebugUtilsMessengerEXT"); + if (vkDestroyDebugUtilsMessengerEXT != nullptr) + vkDestroyDebugUtilsMessengerEXT(mInstance, mDebugMessenger, nullptr); +#endif + + vkDestroyInstance(mInstance, nullptr); +} + +void RendererVK::Initialize() +{ + Renderer::Initialize(); + + // Flip the sign of the projection matrix + mPerspectiveYSign = -1.0f; + + // Required instance extensions + Array required_instance_extensions; + required_instance_extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); + required_instance_extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); + + // Required device extensions + Array required_device_extensions; + required_device_extensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME); + + // Query supported instance extensions + uint32 instance_extension_count = 0; + FatalErrorIfFailed(vkEnumerateInstanceExtensionProperties(nullptr, &instance_extension_count, nullptr)); + Array instance_extensions; + instance_extensions.resize(instance_extension_count); + FatalErrorIfFailed(vkEnumerateInstanceExtensionProperties(nullptr, &instance_extension_count, instance_extensions.data())); + + // Query supported validation layers + uint32 validation_layer_count; + vkEnumerateInstanceLayerProperties(&validation_layer_count, nullptr); + Array validation_layers(validation_layer_count); + vkEnumerateInstanceLayerProperties(&validation_layer_count, validation_layers.data()); + + // Create Vulkan instance + VkInstanceCreateInfo instance_create_info = {}; + instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + +#ifdef JPH_DEBUG + // Enable validation layer if supported + const char *desired_validation_layers[] = { "VK_LAYER_KHRONOS_validation" }; + for (const VkLayerProperties &p : validation_layers) + if (strcmp(desired_validation_layers[0], p.layerName) == 0) + { + instance_create_info.enabledLayerCount = 1; + instance_create_info.ppEnabledLayerNames = desired_validation_layers; + break; + } + + // Setup debug messenger callback if the extension is supported + VkDebugUtilsMessengerCreateInfoEXT messenger_create_info = {}; + for (const VkExtensionProperties &ext : instance_extensions) + if (strcmp(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, ext.extensionName) == 0) + { + messenger_create_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + messenger_create_info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + messenger_create_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_DEVICE_ADDRESS_BINDING_BIT_EXT; + messenger_create_info.pfnUserCallback = sVulkanDebugCallback; + instance_create_info.pNext = &messenger_create_info; + required_instance_extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + break; + } +#endif + + instance_create_info.enabledExtensionCount = (uint32)required_instance_extensions.size(); + instance_create_info.ppEnabledExtensionNames = required_instance_extensions.data(); + FatalErrorIfFailed(vkCreateInstance(&instance_create_info, nullptr, &mInstance)); + +#ifdef JPH_DEBUG + // Finalize debug messenger callback + PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT = (PFN_vkCreateDebugUtilsMessengerEXT)(std::uintptr_t)vkGetInstanceProcAddr(mInstance, "vkCreateDebugUtilsMessengerEXT"); + if (vkCreateDebugUtilsMessengerEXT != nullptr) + FatalErrorIfFailed(vkCreateDebugUtilsMessengerEXT(mInstance, &messenger_create_info, nullptr, &mDebugMessenger)); +#endif + + // Create surface + VkWin32SurfaceCreateInfoKHR surface_create_info = {}; + surface_create_info.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; + surface_create_info.hwnd = mhWnd; + surface_create_info.hinstance = GetModuleHandle(nullptr); + FatalErrorIfFailed(vkCreateWin32SurfaceKHR(mInstance, &surface_create_info, nullptr, &mSurface)); + + // Select device + uint32 device_count = 0; + FatalErrorIfFailed(vkEnumeratePhysicalDevices(mInstance, &device_count, nullptr)); + Array devices; + devices.resize(device_count); + FatalErrorIfFailed(vkEnumeratePhysicalDevices(mInstance, &device_count, devices.data())); + struct Device + { + VkPhysicalDevice mPhysicalDevice; + String mName; + VkSurfaceFormatKHR mFormat; + uint32 mGraphicsQueueIndex; + uint32 mPresentQueueIndex; + int mScore; + }; + Array available_devices; + for (VkPhysicalDevice device : devices) + { + // Get device properties + VkPhysicalDeviceProperties properties; + vkGetPhysicalDeviceProperties(device, &properties); + + // Test if it is an appropriate type + int score = 0; + switch (properties.deviceType) + { + case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: + score = 30; + break; + case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: + score = 20; + break; + case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: + score = 10; + break; + case VK_PHYSICAL_DEVICE_TYPE_OTHER: + case VK_PHYSICAL_DEVICE_TYPE_CPU: + case VK_PHYSICAL_DEVICE_TYPE_MAX_ENUM: + continue; + } + + // Check if the device supports all our required extensions + uint32 device_extension_count; + vkEnumerateDeviceExtensionProperties(device, nullptr, &device_extension_count, nullptr); + Array available_extensions; + available_extensions.resize(device_extension_count); + vkEnumerateDeviceExtensionProperties(device, nullptr, &device_extension_count, available_extensions.data()); + int found_extensions = 0; + for (const char *required_device_extension : required_device_extensions) + for (const VkExtensionProperties &ext : available_extensions) + if (strcmp(required_device_extension, ext.extensionName) == 0) + { + found_extensions++; + break; + } + if (found_extensions != int(required_device_extensions.size())) + continue; + + // Find the right queues + uint32 queue_family_count = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, nullptr); + Array queue_families; + queue_families.resize(queue_family_count); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, queue_families.data()); + uint32 graphics_queue = ~uint32(0); + uint32 present_queue = ~uint32(0); + for (uint32 i = 0; i < uint32(queue_families.size()); ++i) + { + if (queue_families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) + graphics_queue = i; + + VkBool32 present_support = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, mSurface, &present_support); + if (present_support) + present_queue = i; + + if (graphics_queue != ~uint32(0) && present_queue != ~uint32(0)) + break; + } + if (graphics_queue == ~uint32(0) || present_queue == ~uint32(0)) + continue; + + // Select surface format + VkSurfaceFormatKHR selected_format = SelectFormat(device); + if (selected_format.format == VK_FORMAT_UNDEFINED) + continue; + + // Add the device + available_devices.push_back({ device, properties.deviceName, selected_format, graphics_queue, present_queue, score }); + } + if (available_devices.empty()) + FatalError("No Vulkan device found!"); + QuickSort(available_devices.begin(), available_devices.end(), [](const Device &inLHS, const Device &inRHS) { + return inLHS.mScore > inRHS.mScore; + }); + const Device &selected_device = available_devices[0]; + Trace("Selected device: %s", selected_device.mName.c_str()); + mPhysicalDevice = selected_device.mPhysicalDevice; + + // Get memory properties + vkGetPhysicalDeviceMemoryProperties(mPhysicalDevice, &mMemoryProperties); + + // Create device + float queue_priority = 1.0f; + VkDeviceQueueCreateInfo queue_create_info[2] = {}; + for (size_t i = 0; i < std::size(queue_create_info); ++i) + { + queue_create_info[i].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queue_create_info[i].queueCount = 1; + queue_create_info[i].pQueuePriorities = &queue_priority; + } + queue_create_info[0].queueFamilyIndex = selected_device.mGraphicsQueueIndex; + queue_create_info[1].queueFamilyIndex = selected_device.mPresentQueueIndex; + VkPhysicalDeviceFeatures device_features = {}; + device_features.fillModeNonSolid = VK_TRUE; + VkDeviceCreateInfo device_create_info = {}; + device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + device_create_info.queueCreateInfoCount = selected_device.mGraphicsQueueIndex != selected_device.mPresentQueueIndex? 2 : 1; + device_create_info.pQueueCreateInfos = queue_create_info; + device_create_info.enabledLayerCount = instance_create_info.enabledLayerCount; + device_create_info.ppEnabledLayerNames = instance_create_info.ppEnabledLayerNames; + device_create_info.enabledExtensionCount = uint32(required_device_extensions.size()); + device_create_info.ppEnabledExtensionNames = required_device_extensions.data(); + device_create_info.pEnabledFeatures = &device_features; + FatalErrorIfFailed(vkCreateDevice(selected_device.mPhysicalDevice, &device_create_info, nullptr, &mDevice)); + + // Get the queues + mGraphicsQueueIndex = selected_device.mGraphicsQueueIndex; + mPresentQueueIndex = selected_device.mPresentQueueIndex; + vkGetDeviceQueue(mDevice, mGraphicsQueueIndex, 0, &mGraphicsQueue); + vkGetDeviceQueue(mDevice, mPresentQueueIndex, 0, &mPresentQueue); + + VkCommandPoolCreateInfo pool_info = {}; + pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + pool_info.queueFamilyIndex = selected_device.mGraphicsQueueIndex; + FatalErrorIfFailed(vkCreateCommandPool(mDevice, &pool_info, nullptr, &mCommandPool)); + + VkCommandBufferAllocateInfo command_buffer_info = {}; + command_buffer_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + command_buffer_info.commandPool = mCommandPool; + command_buffer_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + command_buffer_info.commandBufferCount = 1; + for (uint32 i = 0; i < cFrameCount; ++i) + FatalErrorIfFailed(vkAllocateCommandBuffers(mDevice, &command_buffer_info, &mCommandBuffers[i])); + + VkSemaphoreCreateInfo semaphore_info = {}; + semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + for (uint32 i = 0; i < cFrameCount; ++i) + { + FatalErrorIfFailed(vkCreateSemaphore(mDevice, &semaphore_info, nullptr, &mImageAvailableSemaphores[i])); + FatalErrorIfFailed(vkCreateSemaphore(mDevice, &semaphore_info, nullptr, &mRenderFinishedSemaphores[i])); + } + + VkFenceCreateInfo fence_info = {}; + fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fence_info.flags = VK_FENCE_CREATE_SIGNALED_BIT; + for (uint32 i = 0; i < cFrameCount; ++i) + FatalErrorIfFailed(vkCreateFence(mDevice, &fence_info, nullptr, &mInFlightFences[i])); + + // Create constant buffer. One per frame to avoid overwriting the constant buffer while the GPU is still using it. + for (uint n = 0; n < cFrameCount; ++n) + { + mVertexShaderConstantBufferProjection[n] = CreateConstantBuffer(sizeof(VertexShaderConstantBuffer)); + mVertexShaderConstantBufferOrtho[n] = CreateConstantBuffer(sizeof(VertexShaderConstantBuffer)); + mPixelShaderConstantBuffer[n] = CreateConstantBuffer(sizeof(PixelShaderConstantBuffer)); + } + + // Create descriptor set layout for the uniform buffers + VkDescriptorSetLayoutBinding ubo_layout_binding[2] = {}; + ubo_layout_binding[0].binding = 0; + ubo_layout_binding[0].descriptorCount = 1; + ubo_layout_binding[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + ubo_layout_binding[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + ubo_layout_binding[1].binding = 1; + ubo_layout_binding[1].descriptorCount = 1; + ubo_layout_binding[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + ubo_layout_binding[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + VkDescriptorSetLayoutCreateInfo ubo_dsl = {}; + ubo_dsl.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + ubo_dsl.bindingCount = std::size(ubo_layout_binding); + ubo_dsl.pBindings = ubo_layout_binding; + FatalErrorIfFailed(vkCreateDescriptorSetLayout(mDevice, &ubo_dsl, nullptr, &mDescriptorSetLayoutUBO)); + + // Create descriptor set layout for the texture binding + VkDescriptorSetLayoutBinding texture_layout_binding = {}; + texture_layout_binding.binding = 0; + texture_layout_binding.descriptorCount = 1; + texture_layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + texture_layout_binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + VkDescriptorSetLayoutCreateInfo texture_dsl = {}; + texture_dsl.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + texture_dsl.bindingCount = 1; + texture_dsl.pBindings = &texture_layout_binding; + FatalErrorIfFailed(vkCreateDescriptorSetLayout(mDevice, &texture_dsl, nullptr, &mDescriptorSetLayoutTexture)); + + // Create pipeline layout + VkPipelineLayoutCreateInfo pipeline_layout = {}; + VkDescriptorSetLayout layout_handles[] = { mDescriptorSetLayoutUBO, mDescriptorSetLayoutTexture }; + pipeline_layout.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipeline_layout.setLayoutCount = std::size(layout_handles); + pipeline_layout.pSetLayouts = layout_handles; + pipeline_layout.pushConstantRangeCount = 0; + FatalErrorIfFailed(vkCreatePipelineLayout(mDevice, &pipeline_layout, nullptr, &mPipelineLayout)); + + // Create descriptor pool + VkDescriptorPoolSize descriptor_pool_size = {}; + descriptor_pool_size.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptor_pool_size.descriptorCount = cFrameCount; + VkDescriptorPoolCreateInfo descriptor_info = {}; + descriptor_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + descriptor_info.poolSizeCount = 1; + descriptor_info.pPoolSizes = &descriptor_pool_size; + descriptor_info.maxSets = 256; + FatalErrorIfFailed(vkCreateDescriptorPool(mDevice, &descriptor_info, nullptr, &mDescriptorPool)); + + // Allocate descriptor sets for 3d rendering + Array layouts(cFrameCount, mDescriptorSetLayoutUBO); + VkDescriptorSetAllocateInfo descriptor_set_alloc_info = {}; + descriptor_set_alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + descriptor_set_alloc_info.descriptorPool = mDescriptorPool; + descriptor_set_alloc_info.descriptorSetCount = cFrameCount; + descriptor_set_alloc_info.pSetLayouts = layouts.data(); + FatalErrorIfFailed(vkAllocateDescriptorSets(mDevice, &descriptor_set_alloc_info, mDescriptorSets)); + for (uint i = 0; i < cFrameCount; i++) + { + VkDescriptorBufferInfo vs_buffer_info = {}; + vs_buffer_info.buffer = mVertexShaderConstantBufferProjection[i]->GetBuffer(); + vs_buffer_info.range = sizeof(VertexShaderConstantBuffer); + + VkDescriptorBufferInfo ps_buffer_info = {}; + ps_buffer_info.buffer = mPixelShaderConstantBuffer[i]->GetBuffer(); + ps_buffer_info.range = sizeof(PixelShaderConstantBuffer); + + VkWriteDescriptorSet descriptor_write[2] = {}; + descriptor_write[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptor_write[0].dstSet = mDescriptorSets[i]; + descriptor_write[0].dstBinding = 0; + descriptor_write[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptor_write[0].descriptorCount = 1; + descriptor_write[0].pBufferInfo = &vs_buffer_info; + descriptor_write[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptor_write[1].dstSet = mDescriptorSets[i]; + descriptor_write[1].dstBinding = 1; + descriptor_write[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptor_write[1].descriptorCount = 1; + descriptor_write[1].pBufferInfo = &ps_buffer_info; + vkUpdateDescriptorSets(mDevice, 2, descriptor_write, 0, nullptr); + } + + // Allocate descriptor sets for 2d rendering + FatalErrorIfFailed(vkAllocateDescriptorSets(mDevice, &descriptor_set_alloc_info, mDescriptorSetsOrtho)); + for (uint i = 0; i < cFrameCount; i++) + { + VkDescriptorBufferInfo vs_buffer_info = {}; + vs_buffer_info.buffer = mVertexShaderConstantBufferOrtho[i]->GetBuffer(); + vs_buffer_info.range = sizeof(VertexShaderConstantBuffer); + + VkWriteDescriptorSet descriptor_write = {}; + descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptor_write.dstSet = mDescriptorSetsOrtho[i]; + descriptor_write.dstBinding = 0; + descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptor_write.descriptorCount = 1; + descriptor_write.pBufferInfo = &vs_buffer_info; + vkUpdateDescriptorSets(mDevice, 1, &descriptor_write, 0, nullptr); + } + + // Create regular texture sampler + VkSamplerCreateInfo sampler_info = {}; + sampler_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + sampler_info.magFilter = VK_FILTER_LINEAR; + sampler_info.minFilter = VK_FILTER_LINEAR; + sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + sampler_info.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + sampler_info.unnormalizedCoordinates = VK_FALSE; + sampler_info.compareEnable = VK_FALSE; + sampler_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + FatalErrorIfFailed(vkCreateSampler(mDevice, &sampler_info, nullptr, &mTextureSamplerRepeat)); + + // Create sampler for shadow maps + sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + sampler_info.compareEnable = VK_TRUE; + sampler_info.compareOp = VK_COMPARE_OP_GREATER_OR_EQUAL; + FatalErrorIfFailed(vkCreateSampler(mDevice, &sampler_info, nullptr, &mTextureSamplerShadow)); + + { + // Create shadow render pass + VkAttachmentDescription shadowmap_attachment = {}; + shadowmap_attachment.format = FindDepthFormat(); + shadowmap_attachment.samples = VK_SAMPLE_COUNT_1_BIT; + shadowmap_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + shadowmap_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + shadowmap_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + shadowmap_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + shadowmap_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + shadowmap_attachment.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + VkAttachmentReference shadowmap_attachment_ref = {}; + shadowmap_attachment_ref.attachment = 0; + shadowmap_attachment_ref.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + VkSubpassDescription subpass_shadow = {}; + subpass_shadow.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass_shadow.pDepthStencilAttachment = &shadowmap_attachment_ref; + VkSubpassDependency dependencies_shadow = {}; + dependencies_shadow.srcSubpass = VK_SUBPASS_EXTERNAL; + dependencies_shadow.dstSubpass = 0; + dependencies_shadow.srcStageMask = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + dependencies_shadow.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + dependencies_shadow.dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependencies_shadow.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + VkRenderPassCreateInfo render_pass_shadow = {}; + render_pass_shadow.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + render_pass_shadow.attachmentCount = 1; + render_pass_shadow.pAttachments = &shadowmap_attachment; + render_pass_shadow.subpassCount = 1; + render_pass_shadow.pSubpasses = &subpass_shadow; + render_pass_shadow.dependencyCount = 1; + render_pass_shadow.pDependencies = &dependencies_shadow; + FatalErrorIfFailed(vkCreateRenderPass(mDevice, &render_pass_shadow, nullptr, &mRenderPassShadow)); + } + + // Create depth only texture (no color buffer, as seen from light) + mShadowMap = new TextureVK(this, cShadowMapSize, cShadowMapSize); + + // Create frame buffer for the shadow pass + VkImageView attachments[] = { mShadowMap->GetImageView() }; + VkFramebufferCreateInfo frame_buffer_info = {}; + frame_buffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + frame_buffer_info.renderPass = mRenderPassShadow; + frame_buffer_info.attachmentCount = std::size(attachments); + frame_buffer_info.pAttachments = attachments; + frame_buffer_info.width = cShadowMapSize; + frame_buffer_info.height = cShadowMapSize; + frame_buffer_info.layers = 1; + FatalErrorIfFailed(vkCreateFramebuffer(mDevice, &frame_buffer_info, nullptr, &mShadowFrameBuffer)); + + { + // Create normal render pass + VkAttachmentDescription attachments_normal[2] = {}; + VkAttachmentDescription &color_attachment = attachments_normal[0]; + color_attachment.format = selected_device.mFormat.format; + color_attachment.samples = VK_SAMPLE_COUNT_1_BIT; + color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + color_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + color_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + color_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + color_attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + VkAttachmentReference color_attachment_ref = {}; + color_attachment_ref.attachment = 0; + color_attachment_ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + VkAttachmentDescription &depth_attachment = attachments_normal[1]; + depth_attachment.format = FindDepthFormat(); + depth_attachment.samples = VK_SAMPLE_COUNT_1_BIT; + depth_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + depth_attachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depth_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + depth_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depth_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + depth_attachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + VkAttachmentReference depth_attachment_ref = {}; + depth_attachment_ref.attachment = 1; + depth_attachment_ref.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + VkSubpassDescription subpass_normal = {}; + subpass_normal.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass_normal.colorAttachmentCount = 1; + subpass_normal.pColorAttachments = &color_attachment_ref; + subpass_normal.pDepthStencilAttachment = &depth_attachment_ref; + VkSubpassDependency dependencies_normal = {}; + dependencies_normal.srcSubpass = VK_SUBPASS_EXTERNAL; + dependencies_normal.dstSubpass = 0; + dependencies_normal.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + dependencies_normal.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + dependencies_normal.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + dependencies_normal.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; + VkRenderPassCreateInfo render_pass_normal = {}; + render_pass_normal.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + render_pass_normal.attachmentCount = std::size(attachments_normal); + render_pass_normal.pAttachments = attachments_normal; + render_pass_normal.subpassCount = 1; + render_pass_normal.pSubpasses = &subpass_normal; + render_pass_normal.dependencyCount = 1; + render_pass_normal.pDependencies = &dependencies_normal; + FatalErrorIfFailed(vkCreateRenderPass(mDevice, &render_pass_normal, nullptr, &mRenderPass)); + } + + // Create the swap chain + CreateSwapChain(mPhysicalDevice); +} + +VkSurfaceFormatKHR RendererVK::SelectFormat(VkPhysicalDevice inDevice) +{ + uint32 format_count; + vkGetPhysicalDeviceSurfaceFormatsKHR(inDevice, mSurface, &format_count, nullptr); + if (format_count == 0) + return { VK_FORMAT_UNDEFINED, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR }; + Array formats; + formats.resize(format_count); + vkGetPhysicalDeviceSurfaceFormatsKHR(inDevice, mSurface, &format_count, formats.data()); + + // Select BGRA8 UNORM format if available, otherwise the 1st format + for (const VkSurfaceFormatKHR &format : formats) + if (format.format == VK_FORMAT_B8G8R8A8_UNORM && format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) + return format; + return formats[0]; +} + +VkFormat RendererVK::FindDepthFormat() +{ + VkFormat candidates[] = { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT }; + for (VkFormat format : candidates) + { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(mPhysicalDevice, format, &props); + + if ((props.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) == VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) + return format; + } + + FatalError("Failed to find format!"); +} + +void RendererVK::CreateSwapChain(VkPhysicalDevice inDevice) +{ + // Select the format + VkSurfaceFormatKHR format = SelectFormat(inDevice); + mSwapChainImageFormat = format.format; + + // Determine swap chain extent + VkSurfaceCapabilitiesKHR capabilities; + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(inDevice, mSurface, &capabilities); + mSwapChainExtent = capabilities.currentExtent; + if (mSwapChainExtent.width == UINT32_MAX || mSwapChainExtent.height == UINT32_MAX) + mSwapChainExtent = { uint32(mWindowWidth), uint32(mWindowHeight) }; + mSwapChainExtent.width = Clamp(mSwapChainExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + mSwapChainExtent.height = Clamp(mSwapChainExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + // Early out if our window has been minimized + if (mSwapChainExtent.width == 0 || mSwapChainExtent.height == 0) + return; + + // Create the swap chain + uint32 image_count = min(capabilities.minImageCount + 1, capabilities.maxImageCount); + VkSwapchainCreateInfoKHR swapchain_create_info = {}; + swapchain_create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + swapchain_create_info.surface = mSurface; + swapchain_create_info.minImageCount = image_count; + swapchain_create_info.imageFormat = format.format; + swapchain_create_info.imageColorSpace = format.colorSpace; + swapchain_create_info.imageExtent = mSwapChainExtent; + swapchain_create_info.imageArrayLayers = 1; + swapchain_create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + uint32 queue_family_indices[] = { mGraphicsQueueIndex, mPresentQueueIndex }; + if (mGraphicsQueueIndex != mPresentQueueIndex) + { + swapchain_create_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + swapchain_create_info.queueFamilyIndexCount = 2; + swapchain_create_info.pQueueFamilyIndices = queue_family_indices; + } + else + { + swapchain_create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + swapchain_create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + swapchain_create_info.preTransform = capabilities.currentTransform; + swapchain_create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + swapchain_create_info.presentMode = VK_PRESENT_MODE_FIFO_KHR; + swapchain_create_info.clipped = VK_TRUE; + FatalErrorIfFailed(vkCreateSwapchainKHR(mDevice, &swapchain_create_info, nullptr, &mSwapChain)); + + // Get the swap chain images + mSwapChainImages.resize(image_count); + vkGetSwapchainImagesKHR(mDevice, mSwapChain, &image_count, mSwapChainImages.data()); + + // Create image views + mSwapChainImageViews.resize(image_count); + for (uint32 i = 0; i < image_count; ++i) + mSwapChainImageViews[i] = CreateImageView(mSwapChainImages[i], mSwapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT); + + // Create depth buffer + VkFormat depth_format = FindDepthFormat(); + CreateImage(mSwapChainExtent.width, mSwapChainExtent.height, depth_format, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, mDepthImage, mDepthImageMemory); + mDepthImageView = CreateImageView(mDepthImage, depth_format, VK_IMAGE_ASPECT_DEPTH_BIT); + + // Create frame buffers for the normal pass + mSwapChainFramebuffers.resize(image_count); + for (size_t i = 0; i < mSwapChainFramebuffers.size(); i++) + { + VkImageView attachments[] = { mSwapChainImageViews[i], mDepthImageView }; + VkFramebufferCreateInfo frame_buffer_info = {}; + frame_buffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + frame_buffer_info.renderPass = mRenderPass; + frame_buffer_info.attachmentCount = std::size(attachments); + frame_buffer_info.pAttachments = attachments; + frame_buffer_info.width = mSwapChainExtent.width; + frame_buffer_info.height = mSwapChainExtent.height; + frame_buffer_info.layers = 1; + FatalErrorIfFailed(vkCreateFramebuffer(mDevice, &frame_buffer_info, nullptr, &mSwapChainFramebuffers[i])); + } +} + +void RendererVK::DestroySwapChain() +{ + // Destroy depth buffer + if (mDepthImageView != VK_NULL_HANDLE) + { + vkDestroyImageView(mDevice, mDepthImageView, nullptr); + vkDestroyImage(mDevice, mDepthImage, nullptr); + vkFreeMemory(mDevice, mDepthImageMemory, nullptr); + } + + for (VkFramebuffer frame_buffer : mSwapChainFramebuffers) + vkDestroyFramebuffer(mDevice, frame_buffer, nullptr); + mSwapChainFramebuffers.clear(); + + for (VkImageView view : mSwapChainImageViews) + vkDestroyImageView(mDevice, view, nullptr); + mSwapChainImageViews.clear(); + + if (mSwapChain != nullptr) + { + vkDestroySwapchainKHR(mDevice, mSwapChain, nullptr); + mSwapChain = nullptr; + } +} + +void RendererVK::OnWindowResize() +{ + Renderer::OnWindowResize(); + + vkDeviceWaitIdle(mDevice); + DestroySwapChain(); + CreateSwapChain(mPhysicalDevice); +} + +void RendererVK::BeginFrame(const CameraState &inCamera, float inWorldScale) +{ + JPH_PROFILE_FUNCTION(); + + Renderer::BeginFrame(inCamera, inWorldScale); + + // If we have no swap chain, bail out + if (mSwapChain == nullptr) + return; + + // Update frame index + mFrameIndex = (mFrameIndex + 1) % cFrameCount; + + // Wait for this frame to complete + vkWaitForFences(mDevice, 1, &mInFlightFences[mFrameIndex], VK_TRUE, UINT64_MAX); + + VkResult result = vkAcquireNextImageKHR(mDevice, mSwapChain, UINT64_MAX, mImageAvailableSemaphores[mFrameIndex], VK_NULL_HANDLE, &mImageIndex); + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) + { + vkDeviceWaitIdle(mDevice); + DestroySwapChain(); + CreateSwapChain(mPhysicalDevice); + if (mSwapChain == nullptr) + return; + result = vkAcquireNextImageKHR(mDevice, mSwapChain, UINT64_MAX, mImageAvailableSemaphores[mFrameIndex], VK_NULL_HANDLE, &mImageIndex); + } + FatalErrorIfFailed(result); + + // Free buffers that weren't used this frame + for (BufferCache::value_type &vt : mBufferCache) + for (BufferVK &bvk : vt.second) + bvk.Free(mDevice); + mBufferCache.clear(); + + // Recycle the buffers that were freed + mBufferCache.swap(mFreedBuffers[mFrameIndex]); + + vkResetFences(mDevice, 1, &mInFlightFences[mFrameIndex]); + + VkCommandBuffer command_buffer = GetCommandBuffer(); + FatalErrorIfFailed(vkResetCommandBuffer(command_buffer, 0)); + + VkCommandBufferBeginInfo command_buffer_begin_info = {}; + command_buffer_begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + FatalErrorIfFailed(vkBeginCommandBuffer(command_buffer, &command_buffer_begin_info)); + + // Begin the shadow pass + VkClearValue clear_value; + clear_value.depthStencil = { 0.0f, 0 }; + VkRenderPassBeginInfo render_pass_begin_info = {}; + render_pass_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + render_pass_begin_info.renderPass = mRenderPassShadow; + render_pass_begin_info.framebuffer = mShadowFrameBuffer; + render_pass_begin_info.renderArea.extent = { cShadowMapSize, cShadowMapSize }; + render_pass_begin_info.clearValueCount = 1; + render_pass_begin_info.pClearValues = &clear_value; + vkCmdBeginRenderPass(command_buffer, &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE); + + // Set constants for vertex shader in projection mode + VertexShaderConstantBuffer *vs = mVertexShaderConstantBufferProjection[mFrameIndex]->Map(); + *vs = mVSBuffer; + mVertexShaderConstantBufferProjection[mFrameIndex]->Unmap(); + + // Set constants for vertex shader in ortho mode + vs = mVertexShaderConstantBufferOrtho[mFrameIndex]->Map(); + *vs = mVSBufferOrtho; + mVertexShaderConstantBufferOrtho[mFrameIndex]->Unmap(); + + // Set constants for pixel shader + PixelShaderConstantBuffer *ps = mPixelShaderConstantBuffer[mFrameIndex]->Map(); + *ps = mPSBuffer; + mPixelShaderConstantBuffer[mFrameIndex]->Unmap(); + + // Set the view port and scissor rect to the shadow map size + UpdateViewPortAndScissorRect(cShadowMapSize, cShadowMapSize); + + // Switch to 3d projection mode + SetProjectionMode(); +} + +void RendererVK::EndShadowPass() +{ + VkCommandBuffer command_buffer = GetCommandBuffer(); + + // End the shadow pass + vkCmdEndRenderPass(command_buffer); + + // Begin the normal render pass + VkClearValue clear_values[2]; + clear_values[0].color = {{ 0.098f, 0.098f, 0.439f, 1.000f }}; + clear_values[1].depthStencil = { 0.0f, 0 }; // Reverse-Z clears to 0 + VkRenderPassBeginInfo render_pass_begin_info = {}; + render_pass_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + render_pass_begin_info.renderPass = mRenderPass; + JPH_ASSERT(mImageIndex < mSwapChainFramebuffers.size()); + render_pass_begin_info.framebuffer = mSwapChainFramebuffers[mImageIndex]; + render_pass_begin_info.renderArea.extent = mSwapChainExtent; + render_pass_begin_info.clearValueCount = std::size(clear_values); + render_pass_begin_info.pClearValues = clear_values; + vkCmdBeginRenderPass(command_buffer, &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE); + + // Set the view port and scissor rect to the screen size + UpdateViewPortAndScissorRect(mSwapChainExtent.width, mSwapChainExtent.height); +} + +void RendererVK::EndFrame() +{ + JPH_PROFILE_FUNCTION(); + + // If we have no swap chain, bail out + if (mSwapChain == nullptr) + { + Renderer::EndFrame(); + return; + } + + VkCommandBuffer command_buffer = GetCommandBuffer(); + vkCmdEndRenderPass(command_buffer); + + FatalErrorIfFailed(vkEndCommandBuffer(command_buffer)); + + VkSemaphore wait_semaphores[] = { mImageAvailableSemaphores[mFrameIndex] }; + VkSemaphore signal_semaphores[] = { mRenderFinishedSemaphores[mFrameIndex] }; + VkPipelineStageFlags wait_stages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; + VkSubmitInfo submit_info = {}; + submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submit_info.waitSemaphoreCount = 1; + submit_info.pWaitSemaphores = wait_semaphores; + submit_info.pWaitDstStageMask = wait_stages; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &command_buffer; + submit_info.signalSemaphoreCount = 1; + submit_info.pSignalSemaphores = signal_semaphores; + FatalErrorIfFailed(vkQueueSubmit(mGraphicsQueue, 1, &submit_info, mInFlightFences[mFrameIndex])); + + VkSwapchainKHR swap_chains[] = { mSwapChain }; + VkPresentInfoKHR present_info = {}; + present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + present_info.waitSemaphoreCount = 1; + present_info.pWaitSemaphores = signal_semaphores; + present_info.swapchainCount = 1; + present_info.pSwapchains = swap_chains; + present_info.pImageIndices = &mImageIndex; + vkQueuePresentKHR(mPresentQueue, &present_info); + + Renderer::EndFrame(); +} + +void RendererVK::SetProjectionMode() +{ + JPH_ASSERT(mInFrame); + + // Bind descriptor set for 3d rendering + vkCmdBindDescriptorSets(GetCommandBuffer(), VK_PIPELINE_BIND_POINT_GRAPHICS, mPipelineLayout, 0, 1, &mDescriptorSets[mFrameIndex], 0, nullptr); +} + +void RendererVK::SetOrthoMode() +{ + JPH_ASSERT(mInFrame); + + // Bind descriptor set for 2d rendering + vkCmdBindDescriptorSets(GetCommandBuffer(), VK_PIPELINE_BIND_POINT_GRAPHICS, mPipelineLayout, 0, 1, &mDescriptorSetsOrtho[mFrameIndex], 0, nullptr); +} + +Ref RendererVK::CreateTexture(const Surface *inSurface) +{ + return new TextureVK(this, inSurface); +} + +Ref RendererVK::CreateVertexShader(const char *inFileName) +{ + Array data = ReadData((String(inFileName) + ".vert.spv").c_str()); + + VkShaderModuleCreateInfo create_info = {}; + create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + create_info.codeSize = data.size(); + create_info.pCode = reinterpret_cast(data.data()); + VkShaderModule shader_module; + FatalErrorIfFailed(vkCreateShaderModule(mDevice, &create_info, nullptr, &shader_module)); + + return new VertexShaderVK(mDevice, shader_module); +} + +Ref RendererVK::CreatePixelShader(const char *inFileName) +{ + Array data = ReadData((String(inFileName) + ".frag.spv").c_str()); + + VkShaderModuleCreateInfo create_info = {}; + create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + create_info.codeSize = data.size(); + create_info.pCode = reinterpret_cast(data.data()); + VkShaderModule shader_module; + FatalErrorIfFailed(vkCreateShaderModule(mDevice, &create_info, nullptr, &shader_module)); + + return new PixelShaderVK(mDevice, shader_module); +} + +unique_ptr RendererVK::CreatePipelineState(const VertexShader *inVertexShader, const PipelineState::EInputDescription *inInputDescription, uint inInputDescriptionCount, const PixelShader *inPixelShader, PipelineState::EDrawPass inDrawPass, PipelineState::EFillMode inFillMode, PipelineState::ETopology inTopology, PipelineState::EDepthTest inDepthTest, PipelineState::EBlendMode inBlendMode, PipelineState::ECullMode inCullMode) +{ + return make_unique(this, static_cast(inVertexShader), inInputDescription, inInputDescriptionCount, static_cast(inPixelShader), inDrawPass, inFillMode, inTopology, inDepthTest, inBlendMode, inCullMode); +} + +RenderPrimitive *RendererVK::CreateRenderPrimitive(PipelineState::ETopology inType) +{ + return new RenderPrimitiveVK(this); +} + +RenderInstances *RendererVK::CreateRenderInstances() +{ + return new RenderInstancesVK(this); +} + +uint32 RendererVK::FindMemoryType(uint32 inTypeFilter, VkMemoryPropertyFlags inProperties) +{ + for (uint32 i = 0; i < mMemoryProperties.memoryTypeCount; i++) + if ((inTypeFilter & (1 << i)) + && (mMemoryProperties.memoryTypes[i].propertyFlags & inProperties) == inProperties) + return i; + + FatalError("Failed to find memory type!"); +} + +void RendererVK::CreateBuffer(VkDeviceSize inSize, VkBufferUsageFlags inUsage, VkMemoryPropertyFlags inProperties, BufferVK &outBuffer) +{ + // Check the cache + BufferCache::iterator i = mBufferCache.find({ inSize, inUsage, inProperties }); + if (i != mBufferCache.end() && !i->second.empty()) + { + outBuffer = i->second.back(); + i->second.pop_back(); + return; + } + + // Create a new buffer + outBuffer.mSize = inSize; + outBuffer.mUsage = inUsage; + outBuffer.mProperties = inProperties; + + VkBufferCreateInfo create_info = {}; + create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + create_info.size = inSize; + create_info.usage = inUsage; + create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + FatalErrorIfFailed(vkCreateBuffer(mDevice, &create_info, nullptr, &outBuffer.mBuffer)); + + VkMemoryRequirements mem_requirements; + vkGetBufferMemoryRequirements(mDevice, outBuffer.mBuffer, &mem_requirements); + + VkMemoryAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + alloc_info.allocationSize = mem_requirements.size; + alloc_info.memoryTypeIndex = FindMemoryType(mem_requirements.memoryTypeBits, inProperties); + + FatalErrorIfFailed(vkAllocateMemory(mDevice, &alloc_info, nullptr, &outBuffer.mMemory)); + + vkBindBufferMemory(mDevice, outBuffer.mBuffer, outBuffer.mMemory, 0); +} + +VkCommandBuffer RendererVK::StartTempCommandBuffer() +{ + VkCommandBufferAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + alloc_info.commandPool = mCommandPool; + alloc_info.commandBufferCount = 1; + + VkCommandBuffer command_buffer; + vkAllocateCommandBuffers(mDevice, &alloc_info, &command_buffer); + + VkCommandBufferBeginInfo begin_info = {}; + begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + vkBeginCommandBuffer(command_buffer, &begin_info); + + return command_buffer; +} + +void RendererVK::EndTempCommandBuffer(VkCommandBuffer inCommandBuffer) +{ + vkEndCommandBuffer(inCommandBuffer); + + VkSubmitInfo submit_info = {}; + submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &inCommandBuffer; + + vkQueueSubmit(mGraphicsQueue, 1, &submit_info, VK_NULL_HANDLE); + vkQueueWaitIdle(mGraphicsQueue); // Inefficient, but we only use this during initialization + + vkFreeCommandBuffers(mDevice, mCommandPool, 1, &inCommandBuffer); +} + +void RendererVK::CopyBuffer(VkBuffer inSrc, VkBuffer inDst, VkDeviceSize inSize) +{ + VkCommandBuffer command_buffer = StartTempCommandBuffer(); + + VkBufferCopy region = {}; + region.size = inSize; + vkCmdCopyBuffer(command_buffer, inSrc, inDst, 1, ®ion); + + EndTempCommandBuffer(command_buffer); +} + +void RendererVK::CreateDeviceLocalBuffer(const void *inData, VkDeviceSize inSize, VkBufferUsageFlags inUsage, BufferVK &outBuffer) +{ + BufferVK staging_buffer; + CreateBuffer(inSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, staging_buffer); + + void *data; + vkMapMemory(mDevice, staging_buffer.mMemory, 0, inSize, 0, &data); + memcpy(data, inData, (size_t)inSize); + vkUnmapMemory(mDevice, staging_buffer.mMemory); + + CreateBuffer(inSize, inUsage | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, outBuffer); + + CopyBuffer(staging_buffer.mBuffer, outBuffer.mBuffer, inSize); + + FreeBuffer(staging_buffer); +} + +void RendererVK::FreeBuffer(BufferVK &ioBuffer) +{ + if (ioBuffer.mBuffer != VK_NULL_HANDLE) + { + JPH_ASSERT(mFrameIndex < cFrameCount); + mFreedBuffers[mFrameIndex][{ ioBuffer.mSize, ioBuffer.mUsage, ioBuffer.mProperties }].push_back(ioBuffer); + } +} + +unique_ptr RendererVK::CreateConstantBuffer(VkDeviceSize inBufferSize) +{ + return make_unique(this, inBufferSize); +} + +VkImageView RendererVK::CreateImageView(VkImage inImage, VkFormat inFormat, VkImageAspectFlags inAspectFlags) +{ + VkImageViewCreateInfo view_info = {}; + view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + view_info.image = inImage; + view_info.viewType = VK_IMAGE_VIEW_TYPE_2D; + view_info.format = inFormat; + view_info.subresourceRange.aspectMask = inAspectFlags; + view_info.subresourceRange.levelCount = 1; + view_info.subresourceRange.layerCount = 1; + + VkImageView image_view; + FatalErrorIfFailed(vkCreateImageView(mDevice, &view_info, nullptr, &image_view)); + + return image_view; +} + +void RendererVK::CreateImage(uint32 inWidth, uint32 inHeight, VkFormat inFormat, VkImageTiling inTiling, VkImageUsageFlags inUsage, VkMemoryPropertyFlags inProperties, VkImage &outImage, VkDeviceMemory &outMemory) +{ + VkImageCreateInfo image_info = {}; + image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + image_info.imageType = VK_IMAGE_TYPE_2D; + image_info.extent.width = inWidth; + image_info.extent.height = inHeight; + image_info.extent.depth = 1; + image_info.mipLevels = 1; + image_info.arrayLayers = 1; + image_info.format = inFormat; + image_info.tiling = inTiling; + image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + image_info.usage = inUsage; + image_info.samples = VK_SAMPLE_COUNT_1_BIT; + image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + FatalErrorIfFailed(vkCreateImage(mDevice, &image_info, nullptr, &outImage)); + + VkMemoryRequirements mem_requirements; + vkGetImageMemoryRequirements(mDevice, outImage, &mem_requirements); + + VkMemoryAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + alloc_info.allocationSize = mem_requirements.size; + alloc_info.memoryTypeIndex = FindMemoryType(mem_requirements.memoryTypeBits, inProperties); + FatalErrorIfFailed(vkAllocateMemory(mDevice, &alloc_info, nullptr, &outMemory)); + + vkBindImageMemory(mDevice, outImage, outMemory, 0); +} + +void RendererVK::UpdateViewPortAndScissorRect(uint32 inWidth, uint32 inHeight) +{ + VkCommandBuffer command_buffer = GetCommandBuffer(); + + // Update the view port rect + VkViewport viewport = {}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)inWidth; + viewport.height = (float)inHeight; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(command_buffer, 0, 1, &viewport); + + // Update the scissor rect + VkRect2D scissor = {}; + scissor.extent = { inWidth, inHeight }; + vkCmdSetScissor(command_buffer, 0, 1, &scissor); +} diff --git a/TestFramework/Renderer/VK/RendererVK.h b/TestFramework/Renderer/VK/RendererVK.h new file mode 100644 index 000000000..bae175596 --- /dev/null +++ b/TestFramework/Renderer/VK/RendererVK.h @@ -0,0 +1,123 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include +#include +#include + +#include + +/// Vulkan renderer +class RendererVK : public Renderer +{ +public: + /// Destructor + virtual ~RendererVK() override; + + // See: Renderer + virtual void Initialize() override; + virtual void BeginFrame(const CameraState &inCamera, float inWorldScale) override; + virtual void EndShadowPass() override; + virtual void EndFrame() override; + virtual void SetProjectionMode() override; + virtual void SetOrthoMode() override; + virtual Ref CreateTexture(const Surface *inSurface) override; + virtual Ref CreateVertexShader(const char *inFileName) override; + virtual Ref CreatePixelShader(const char *inFileName) override; + virtual unique_ptr CreatePipelineState(const VertexShader *inVertexShader, const PipelineState::EInputDescription *inInputDescription, uint inInputDescriptionCount, const PixelShader *inPixelShader, PipelineState::EDrawPass inDrawPass, PipelineState::EFillMode inFillMode, PipelineState::ETopology inTopology, PipelineState::EDepthTest inDepthTest, PipelineState::EBlendMode inBlendMode, PipelineState::ECullMode inCullMode) override; + virtual RenderPrimitive * CreateRenderPrimitive(PipelineState::ETopology inType) override; + virtual RenderInstances * CreateRenderInstances() override; + virtual Texture * GetShadowMap() const override { return mShadowMap.GetPtr(); } + virtual void OnWindowResize() override; + + VkDevice GetDevice() const { return mDevice; } + VkDescriptorPool GetDescriptorPool() const { return mDescriptorPool; } + VkDescriptorSetLayout GetDescriptorSetLayoutTexture() const { return mDescriptorSetLayoutTexture; } + VkSampler GetTextureSamplerRepeat() const { return mTextureSamplerRepeat; } + VkSampler GetTextureSamplerShadow() const { return mTextureSamplerShadow; } + VkRenderPass GetRenderPassShadow() const { return mRenderPassShadow; } + VkRenderPass GetRenderPass() const { return mRenderPass; } + VkPipelineLayout GetPipelineLayout() const { return mPipelineLayout; } + VkCommandBuffer GetCommandBuffer() { JPH_ASSERT(mInFrame); return mCommandBuffers[mFrameIndex]; } + VkCommandBuffer StartTempCommandBuffer(); + void EndTempCommandBuffer(VkCommandBuffer inCommandBuffer); + void CreateBuffer(VkDeviceSize inSize, VkBufferUsageFlags inUsage, VkMemoryPropertyFlags inProperties, BufferVK &outBuffer); + void CopyBuffer(VkBuffer inSrc, VkBuffer inDst, VkDeviceSize inSize); + void CreateDeviceLocalBuffer(const void *inData, VkDeviceSize inSize, VkBufferUsageFlags inUsage, BufferVK &outBuffer); + void FreeBuffer(BufferVK &ioBuffer); + unique_ptr CreateConstantBuffer(VkDeviceSize inBufferSize); + void CreateImage(uint32 inWidth, uint32 inHeight, VkFormat inFormat, VkImageTiling inTiling, VkImageUsageFlags inUsage, VkMemoryPropertyFlags inProperties, VkImage &outImage, VkDeviceMemory &outMemory); + VkImageView CreateImageView(VkImage inImage, VkFormat inFormat, VkImageAspectFlags inAspectFlags); + VkFormat FindDepthFormat(); + +private: + uint32 FindMemoryType(uint32 inTypeFilter, VkMemoryPropertyFlags inProperties); + VkSurfaceFormatKHR SelectFormat(VkPhysicalDevice inDevice); + void CreateSwapChain(VkPhysicalDevice inDevice); + void DestroySwapChain(); + void UpdateViewPortAndScissorRect(uint32 inWidth, uint32 inHeight); + + VkInstance mInstance = VK_NULL_HANDLE; + VkDebugUtilsMessengerEXT mDebugMessenger = VK_NULL_HANDLE; + VkPhysicalDevice mPhysicalDevice = VK_NULL_HANDLE; + VkPhysicalDeviceMemoryProperties mMemoryProperties; + VkDevice mDevice = VK_NULL_HANDLE; + uint32 mGraphicsQueueIndex = 0; + uint32 mPresentQueueIndex = 0; + VkQueue mGraphicsQueue = VK_NULL_HANDLE; + VkQueue mPresentQueue = VK_NULL_HANDLE; + VkSurfaceKHR mSurface = VK_NULL_HANDLE; + VkSwapchainKHR mSwapChain = VK_NULL_HANDLE; + Array mSwapChainImages; + VkFormat mSwapChainImageFormat; + VkExtent2D mSwapChainExtent; + Array mSwapChainImageViews; + VkImage mDepthImage = VK_NULL_HANDLE; + VkDeviceMemory mDepthImageMemory = VK_NULL_HANDLE; + VkImageView mDepthImageView = VK_NULL_HANDLE; + VkDescriptorSetLayout mDescriptorSetLayoutUBO = VK_NULL_HANDLE; + VkDescriptorSetLayout mDescriptorSetLayoutTexture = VK_NULL_HANDLE; + VkDescriptorPool mDescriptorPool = VK_NULL_HANDLE; + VkDescriptorSet mDescriptorSets[cFrameCount]; + VkDescriptorSet mDescriptorSetsOrtho[cFrameCount]; + VkSampler mTextureSamplerShadow = VK_NULL_HANDLE; + VkSampler mTextureSamplerRepeat = VK_NULL_HANDLE; + VkRenderPass mRenderPassShadow = VK_NULL_HANDLE; + VkRenderPass mRenderPass = VK_NULL_HANDLE; + VkPipelineLayout mPipelineLayout = VK_NULL_HANDLE; + VkFramebuffer mShadowFrameBuffer = VK_NULL_HANDLE; + Array mSwapChainFramebuffers; + uint32 mImageIndex = 0; + VkCommandPool mCommandPool = VK_NULL_HANDLE; + VkCommandBuffer mCommandBuffers[cFrameCount]; + VkSemaphore mImageAvailableSemaphores[cFrameCount]; + VkSemaphore mRenderFinishedSemaphores[cFrameCount]; + VkFence mInFlightFences[cFrameCount]; + Ref mShadowMap; + unique_ptr mVertexShaderConstantBufferProjection[cFrameCount]; + unique_ptr mVertexShaderConstantBufferOrtho[cFrameCount]; + unique_ptr mPixelShaderConstantBuffer[cFrameCount]; + + struct Key + { + bool operator == (const Key &inRHS) const + { + return mSize == inRHS.mSize && mUsage == inRHS.mUsage && mProperties == inRHS.mProperties; + } + + VkDeviceSize mSize; + VkBufferUsageFlags mUsage; + VkMemoryPropertyFlags mProperties; + }; + + JPH_MAKE_HASH_STRUCT(Key, KeyHasher, t.mSize, t.mUsage, t.mProperties); + + using BufferCache = UnorderedMap, KeyHasher>; + + BufferCache mFreedBuffers[cFrameCount]; + BufferCache mBufferCache; +}; diff --git a/TestFramework/Renderer/VK/TextureVK.cpp b/TestFramework/Renderer/VK/TextureVK.cpp new file mode 100644 index 000000000..4aab35c70 --- /dev/null +++ b/TestFramework/Renderer/VK/TextureVK.cpp @@ -0,0 +1,180 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#include + +#include +#include +#include +#include + +TextureVK::TextureVK(RendererVK *inRenderer, const Surface *inSurface) : + Texture(inSurface->GetWidth(), inSurface->GetHeight()), + mRenderer(inRenderer) +{ + ESurfaceFormat format = inSurface->GetFormat(); + VkFormat vk_format = VK_FORMAT_B8G8R8A8_UNORM; + switch (format) + { + case ESurfaceFormat::A4L4: vk_format = VK_FORMAT_R8G8_UNORM; format = ESurfaceFormat::A8L8; break; + case ESurfaceFormat::L8: vk_format = VK_FORMAT_R8_UNORM; break; + case ESurfaceFormat::A8: vk_format = VK_FORMAT_A8_UNORM_KHR; break; + case ESurfaceFormat::A8L8: vk_format = VK_FORMAT_R8G8_UNORM; break; + case ESurfaceFormat::R5G6B5: vk_format = VK_FORMAT_B5G6R5_UNORM_PACK16; break; + case ESurfaceFormat::X1R5G5B5: vk_format = VK_FORMAT_B5G5R5A1_UNORM_PACK16; format = ESurfaceFormat::A1R5G5B5; break; + case ESurfaceFormat::X4R4G4B4: vk_format = VK_FORMAT_B4G4R4A4_UNORM_PACK16; format = ESurfaceFormat::A4R4G4B4; break; + case ESurfaceFormat::A1R5G5B5: vk_format = VK_FORMAT_B5G5R5A1_UNORM_PACK16; break; + case ESurfaceFormat::A4R4G4B4: vk_format = VK_FORMAT_B4G4R4A4_UNORM_PACK16; break; + case ESurfaceFormat::R8G8B8: vk_format = VK_FORMAT_B8G8R8_UNORM; break; + case ESurfaceFormat::B8G8R8: vk_format = VK_FORMAT_B8G8R8_UNORM; break; + case ESurfaceFormat::X8R8G8B8: vk_format = VK_FORMAT_B8G8R8A8_UNORM; format = ESurfaceFormat::A8R8G8B8; break; + case ESurfaceFormat::X8B8G8R8: vk_format = VK_FORMAT_B8G8R8A8_UNORM; format = ESurfaceFormat::X8R8G8B8; break; + case ESurfaceFormat::A8R8G8B8: vk_format = VK_FORMAT_B8G8R8A8_UNORM; break; + case ESurfaceFormat::A8B8G8R8: vk_format = VK_FORMAT_B8G8R8A8_UNORM; format = ESurfaceFormat::A8R8G8B8; break; + case ESurfaceFormat::Invalid: + default: JPH_ASSERT(false); break; + } + + // Blit the surface to another temporary surface if the format changed + const Surface *surface = inSurface; + Ref tmp; + if (format != inSurface->GetFormat()) + { + tmp = new SoftwareSurface(mWidth, mHeight, format); + BlitSurface(inSurface, tmp); + surface = tmp; + } + + int bpp = surface->GetBytesPerPixel(); + VkDeviceSize image_size = VkDeviceSize(mWidth) * mHeight * bpp; + VkDevice device = mRenderer->GetDevice(); + + BufferVK staging_buffer; + mRenderer->CreateBuffer(image_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, staging_buffer); + + // Copy data to upload texture + surface->Lock(ESurfaceLockMode::Read); + void *data; + vkMapMemory(device, staging_buffer.mMemory, 0, image_size, 0, &data); + for (int y = 0; y < mHeight; ++y) + memcpy(reinterpret_cast(data) + y * mWidth * bpp, surface->GetData() + y * surface->GetStride(), mWidth * bpp); + vkUnmapMemory(device, staging_buffer.mMemory); + surface->UnLock(); + + // Create destination image + mRenderer->CreateImage(mWidth, mHeight, vk_format, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, mImage, mImageMemory); + + VkCommandBuffer command_buffer = mRenderer->StartTempCommandBuffer(); + + // Make the image suitable for transferring to + TransitionImageLayout(command_buffer, mImage, vk_format, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + + // Copy the data to the destination image + VkBufferImageCopy region = {}; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.layerCount = 1; + region.imageExtent = { uint32(mWidth), uint32(mHeight), 1 }; + vkCmdCopyBufferToImage(command_buffer, staging_buffer.mBuffer, mImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + + // Make the image suitable for sampling + TransitionImageLayout(command_buffer, mImage, vk_format, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + mRenderer->EndTempCommandBuffer(command_buffer); + + // Destroy temporary buffer + mRenderer->FreeBuffer(staging_buffer); + + CreateImageViewAndDescriptorSet(vk_format, VK_IMAGE_ASPECT_COLOR_BIT, mRenderer->GetTextureSamplerRepeat()); +} + +TextureVK::TextureVK(RendererVK *inRenderer, int inWidth, int inHeight) : + Texture(inWidth, inHeight), + mRenderer(inRenderer) +{ + VkFormat vk_format = mRenderer->FindDepthFormat(); + + // Create render target + mRenderer->CreateImage(mWidth, mHeight, vk_format, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, mImage, mImageMemory); + + CreateImageViewAndDescriptorSet(vk_format, VK_IMAGE_ASPECT_DEPTH_BIT, mRenderer->GetTextureSamplerShadow()); +} + +TextureVK::~TextureVK() +{ + if (mImage != VK_NULL_HANDLE) + { + VkDevice device = mRenderer->GetDevice(); + + vkDeviceWaitIdle(device); + + vkDestroyImageView(device, mImageView, nullptr); + vkDestroyImage(device, mImage, nullptr); + vkFreeMemory(device, mImageMemory, nullptr); + } +} + +void TextureVK::Bind() const +{ + if (mDescriptorSet != VK_NULL_HANDLE) + vkCmdBindDescriptorSets(mRenderer->GetCommandBuffer(), VK_PIPELINE_BIND_POINT_GRAPHICS, mRenderer->GetPipelineLayout(), 1, 1, &mDescriptorSet, 0, nullptr); +} + +void TextureVK::CreateImageViewAndDescriptorSet(VkFormat inFormat, VkImageAspectFlags inAspectFlags, VkSampler inSampler) +{ + VkDevice device = mRenderer->GetDevice(); + + // Create image view + mImageView = mRenderer->CreateImageView(mImage, inFormat, inAspectFlags); + + // Allocate descriptor set for binding the texture + VkDescriptorSetLayout layout = mRenderer->GetDescriptorSetLayoutTexture(); + VkDescriptorSetAllocateInfo descriptor_set_alloc_info = {}; + descriptor_set_alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + descriptor_set_alloc_info.descriptorPool = mRenderer->GetDescriptorPool(); + descriptor_set_alloc_info.descriptorSetCount = 1; + descriptor_set_alloc_info.pSetLayouts = &layout; + FatalErrorIfFailed(vkAllocateDescriptorSets(device, &descriptor_set_alloc_info, &mDescriptorSet)); + + VkDescriptorImageInfo image_info = {}; + image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + image_info.imageView = mImageView; + image_info.sampler = inSampler; + + VkWriteDescriptorSet descriptor_write = {}; + descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptor_write.dstSet = mDescriptorSet; + descriptor_write.dstBinding = 0; + descriptor_write.dstArrayElement = 0; + descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + descriptor_write.descriptorCount = 1; + descriptor_write.pImageInfo = &image_info; + vkUpdateDescriptorSets(device, 1, &descriptor_write, 0, nullptr); +} + +void TextureVK::TransitionImageLayout(VkCommandBuffer inCommandBuffer, VkImage inImage, VkFormat inFormat, VkImageLayout inOldLayout, VkImageLayout inNewLayout) +{ + VkImageMemoryBarrier barrier = {}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.oldLayout = inOldLayout; + barrier.newLayout = inNewLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = inImage; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.layerCount = 1; + + if (inOldLayout == VK_IMAGE_LAYOUT_UNDEFINED && inNewLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) + { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + vkCmdPipelineBarrier(inCommandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); + } + else if (inOldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && inNewLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) + { + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + vkCmdPipelineBarrier(inCommandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); + } +} diff --git a/TestFramework/Renderer/VK/TextureVK.h b/TestFramework/Renderer/VK/TextureVK.h new file mode 100644 index 000000000..46e1ef414 --- /dev/null +++ b/TestFramework/Renderer/VK/TextureVK.h @@ -0,0 +1,35 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +#include + +class RendererVK; + +class TextureVK : public Texture +{ +public: + /// Constructor, called by Renderer::CreateTextureVK + TextureVK(RendererVK *inRenderer, const Surface *inSurface); // Create a normal TextureVK + TextureVK(RendererVK *inRenderer, int inWidth, int inHeight); // Create a render target (depth only) + virtual ~TextureVK() override; + + /// Bind texture to the pixel shader + virtual void Bind() const override; + + VkImageView GetImageView() const { return mImageView; } + +private: + void CreateImageViewAndDescriptorSet(VkFormat inFormat, VkImageAspectFlags inAspectFlags, VkSampler inSampler); + void TransitionImageLayout(VkCommandBuffer inCommandBuffer, VkImage inImage, VkFormat inFormat, VkImageLayout inOldLayout, VkImageLayout inNewLayout); + + RendererVK * mRenderer; + VkImage mImage = VK_NULL_HANDLE; + VkDeviceMemory mImageMemory = VK_NULL_HANDLE; + VkImageView mImageView = VK_NULL_HANDLE; + VkDescriptorSet mDescriptorSet = VK_NULL_HANDLE; +}; diff --git a/TestFramework/Renderer/VK/VertexShaderVK.h b/TestFramework/Renderer/VK/VertexShaderVK.h new file mode 100644 index 000000000..af9ea5f3c --- /dev/null +++ b/TestFramework/Renderer/VK/VertexShaderVK.h @@ -0,0 +1,34 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +#include + +/// Vertex shader handle for Vulkan +class VertexShaderVK : public VertexShader +{ +public: + /// Constructor + VertexShaderVK(VkDevice inDevice, VkShaderModule inShaderModule) : + mDevice(inDevice), + mStageInfo() + { + mStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + mStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + mStageInfo.module = inShaderModule; + mStageInfo.pName = "main"; + } + + /// Destructor + virtual ~VertexShaderVK() override + { + vkDestroyShaderModule(mDevice, mStageInfo.module, nullptr); + } + + VkDevice mDevice; + VkPipelineShaderStageCreateInfo mStageInfo; +}; diff --git a/TestFramework/Renderer/VertexShader.h b/TestFramework/Renderer/VertexShader.h new file mode 100644 index 000000000..ffff86b21 --- /dev/null +++ b/TestFramework/Renderer/VertexShader.h @@ -0,0 +1,15 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +/// Vertex shader handle +class VertexShader : public RefTarget +{ +public: + /// Destructor + virtual ~VertexShader() = default; +}; diff --git a/TestFramework/TestFramework.cmake b/TestFramework/TestFramework.cmake index 3564f11f6..e06825346 100644 --- a/TestFramework/TestFramework.cmake +++ b/TestFramework/TestFramework.cmake @@ -24,27 +24,38 @@ set(TEST_FRAMEWORK_SRC_FILES ${TEST_FRAMEWORK_ROOT}/Input/Mouse.h ${TEST_FRAMEWORK_ROOT}/Math/Perlin.cpp ${TEST_FRAMEWORK_ROOT}/Math/Perlin.h - ${TEST_FRAMEWORK_ROOT}/Renderer/ConstantBuffer.cpp - ${TEST_FRAMEWORK_ROOT}/Renderer/ConstantBuffer.h - ${TEST_FRAMEWORK_ROOT}/Renderer/CommandQueue.h - ${TEST_FRAMEWORK_ROOT}/Renderer/DescriptorHeap.h - ${TEST_FRAMEWORK_ROOT}/Renderer/FatalErrorIfFailed.cpp - ${TEST_FRAMEWORK_ROOT}/Renderer/FatalErrorIfFailed.h + ${TEST_FRAMEWORK_ROOT}/Renderer/DebugRendererImp.cpp + ${TEST_FRAMEWORK_ROOT}/Renderer/DebugRendererImp.h + ${TEST_FRAMEWORK_ROOT}/Renderer/DX12/ConstantBufferDX12.cpp + ${TEST_FRAMEWORK_ROOT}/Renderer/DX12/ConstantBufferDX12.h + ${TEST_FRAMEWORK_ROOT}/Renderer/DX12/CommandQueueDX12.h + ${TEST_FRAMEWORK_ROOT}/Renderer/DX12/DescriptorHeapDX12.h + ${TEST_FRAMEWORK_ROOT}/Renderer/DX12/FatalErrorIfFailedDX12.cpp + ${TEST_FRAMEWORK_ROOT}/Renderer/DX12/FatalErrorIfFailedDX12.h + ${TEST_FRAMEWORK_ROOT}/Renderer/DX12/PipelineStateDX12.cpp + ${TEST_FRAMEWORK_ROOT}/Renderer/DX12/PipelineStateDX12.h + ${TEST_FRAMEWORK_ROOT}/Renderer/DX12/PixelShaderDX12.h + ${TEST_FRAMEWORK_ROOT}/Renderer/DX12/RendererDX12.cpp + ${TEST_FRAMEWORK_ROOT}/Renderer/DX12/RendererDX12.h + ${TEST_FRAMEWORK_ROOT}/Renderer/DX12/RenderInstancesDX12.cpp + ${TEST_FRAMEWORK_ROOT}/Renderer/DX12/RenderInstancesDX12.h + ${TEST_FRAMEWORK_ROOT}/Renderer/DX12/RenderPrimitiveDX12.cpp + ${TEST_FRAMEWORK_ROOT}/Renderer/DX12/RenderPrimitiveDX12.h + ${TEST_FRAMEWORK_ROOT}/Renderer/DX12/TextureDX12.cpp + ${TEST_FRAMEWORK_ROOT}/Renderer/DX12/TextureDX12.h + ${TEST_FRAMEWORK_ROOT}/Renderer/DX12/VertexShaderDX12.h ${TEST_FRAMEWORK_ROOT}/Renderer/Font.cpp ${TEST_FRAMEWORK_ROOT}/Renderer/Font.h ${TEST_FRAMEWORK_ROOT}/Renderer/Frustum.h - ${TEST_FRAMEWORK_ROOT}/Renderer/PipelineState.cpp ${TEST_FRAMEWORK_ROOT}/Renderer/PipelineState.h + ${TEST_FRAMEWORK_ROOT}/Renderer/PixelShader.h ${TEST_FRAMEWORK_ROOT}/Renderer/Renderer.cpp ${TEST_FRAMEWORK_ROOT}/Renderer/Renderer.h - ${TEST_FRAMEWORK_ROOT}/Renderer/RenderInstances.cpp ${TEST_FRAMEWORK_ROOT}/Renderer/RenderInstances.h ${TEST_FRAMEWORK_ROOT}/Renderer/RenderPrimitive.cpp ${TEST_FRAMEWORK_ROOT}/Renderer/RenderPrimitive.h - ${TEST_FRAMEWORK_ROOT}/Renderer/Texture.cpp ${TEST_FRAMEWORK_ROOT}/Renderer/Texture.h - ${TEST_FRAMEWORK_ROOT}/Renderer/DebugRendererImp.cpp - ${TEST_FRAMEWORK_ROOT}/Renderer/DebugRendererImp.h + ${TEST_FRAMEWORK_ROOT}/Renderer/VertexShader.h ${TEST_FRAMEWORK_ROOT}/TestFramework.cmake ${TEST_FRAMEWORK_ROOT}/TestFramework.h ${TEST_FRAMEWORK_ROOT}/UI/UIAnimation.cpp @@ -83,27 +94,24 @@ set(TEST_FRAMEWORK_SRC_FILES ${TEST_FRAMEWORK_ROOT}/Utils/ReadData.h ) -# Group source files -source_group(TREE ${TEST_FRAMEWORK_ROOT} FILES ${TEST_FRAMEWORK_SRC_FILES}) - # All shaders set(TEST_FRAMEWORK_SRC_FILES_SHADERS ${PHYSICS_REPO_ROOT}/Assets/Shaders/VertexConstants.h ) -# Vertex shaders -set(TEST_FRAMEWORK_VERTEX_SHADERS +# HLSL vertex shaders +set(TEST_FRAMEWORK_HLSL_VERTEX_SHADERS ${PHYSICS_REPO_ROOT}/Assets/Shaders/FontVertexShader.hlsl ${PHYSICS_REPO_ROOT}/Assets/Shaders/LineVertexShader.hlsl ${PHYSICS_REPO_ROOT}/Assets/Shaders/TriangleDepthVertexShader.hlsl ${PHYSICS_REPO_ROOT}/Assets/Shaders/TriangleVertexShader.hlsl ${PHYSICS_REPO_ROOT}/Assets/Shaders/UIVertexShader.hlsl ) -set(TEST_FRAMEWORK_SRC_FILES_SHADERS ${TEST_FRAMEWORK_SRC_FILES_SHADERS} ${TEST_FRAMEWORK_VERTEX_SHADERS}) -set_source_files_properties(${TEST_FRAMEWORK_VERTEX_SHADERS} PROPERTIES VS_SHADER_FLAGS "/WX /T vs_5_0") +set(TEST_FRAMEWORK_SRC_FILES_SHADERS ${TEST_FRAMEWORK_SRC_FILES_SHADERS} ${TEST_FRAMEWORK_HLSL_VERTEX_SHADERS}) +set_source_files_properties(${TEST_FRAMEWORK_HLSL_VERTEX_SHADERS} PROPERTIES VS_SHADER_FLAGS "/WX /T vs_5_0") -# Pixel shaders -set(TEST_FRAMEWORK_PIXEL_SHADERS +# HLSL pixel shaders +set(TEST_FRAMEWORK_HLSL_PIXEL_SHADERS ${PHYSICS_REPO_ROOT}/Assets/Shaders/FontPixelShader.hlsl ${PHYSICS_REPO_ROOT}/Assets/Shaders/LinePixelShader.hlsl ${PHYSICS_REPO_ROOT}/Assets/Shaders/TriangleDepthPixelShader.hlsl @@ -111,14 +119,83 @@ set(TEST_FRAMEWORK_PIXEL_SHADERS ${PHYSICS_REPO_ROOT}/Assets/Shaders/UIPixelShader.hlsl ${PHYSICS_REPO_ROOT}/Assets/Shaders/UIPixelShaderUntextured.hlsl ) -set(TEST_FRAMEWORK_SRC_FILES_SHADERS ${TEST_FRAMEWORK_SRC_FILES_SHADERS} ${TEST_FRAMEWORK_PIXEL_SHADERS}) -set_source_files_properties(${TEST_FRAMEWORK_PIXEL_SHADERS} PROPERTIES VS_SHADER_FLAGS "/WX /T ps_5_0") +set(TEST_FRAMEWORK_SRC_FILES_SHADERS ${TEST_FRAMEWORK_SRC_FILES_SHADERS} ${TEST_FRAMEWORK_HLSL_PIXEL_SHADERS}) +set_source_files_properties(${TEST_FRAMEWORK_HLSL_PIXEL_SHADERS} PROPERTIES VS_SHADER_FLAGS "/WX /T ps_5_0") + +# Include the Vulkan library +find_package(Vulkan) +if (Vulkan_FOUND) + # Vulkan source files + set(TEST_FRAMEWORK_SRC_FILES + ${TEST_FRAMEWORK_SRC_FILES} + ${TEST_FRAMEWORK_ROOT}/Renderer/VK/BufferVK.h + ${TEST_FRAMEWORK_ROOT}/Renderer/VK/ConstantBufferVK.cpp + ${TEST_FRAMEWORK_ROOT}/Renderer/VK/ConstantBufferVK.h + ${TEST_FRAMEWORK_ROOT}/Renderer/VK/FatalErrorIfFailedVK.cpp + ${TEST_FRAMEWORK_ROOT}/Renderer/VK/FatalErrorIfFailedVK.h + ${TEST_FRAMEWORK_ROOT}/Renderer/VK/PipelineStateVK.cpp + ${TEST_FRAMEWORK_ROOT}/Renderer/VK/PipelineStateVK.h + ${TEST_FRAMEWORK_ROOT}/Renderer/VK/PixelShaderVK.h + ${TEST_FRAMEWORK_ROOT}/Renderer/VK/RendererVK.cpp + ${TEST_FRAMEWORK_ROOT}/Renderer/VK/RendererVK.h + ${TEST_FRAMEWORK_ROOT}/Renderer/VK/RenderInstancesVK.cpp + ${TEST_FRAMEWORK_ROOT}/Renderer/VK/RenderInstancesVK.h + ${TEST_FRAMEWORK_ROOT}/Renderer/VK/RenderPrimitiveVK.cpp + ${TEST_FRAMEWORK_ROOT}/Renderer/VK/RenderPrimitiveVK.h + ${TEST_FRAMEWORK_ROOT}/Renderer/VK/TextureVK.cpp + ${TEST_FRAMEWORK_ROOT}/Renderer/VK/TextureVK.h + ${TEST_FRAMEWORK_ROOT}/Renderer/VK/VertexShaderVK.h + ) + + # GLSL headers + set(TEST_FRAMEWORK_SRC_FILES_SHADERS + ${TEST_FRAMEWORK_SRC_FILES_SHADERS} + ${PHYSICS_REPO_ROOT}/Assets/Shaders/VertexConstantsVK.h + ) + + # GLSL shaders + set(TEST_FRAMEWORK_GLSL_SHADERS + ${PHYSICS_REPO_ROOT}/Assets/Shaders/FontVertexShader.vert + ${PHYSICS_REPO_ROOT}/Assets/Shaders/LineVertexShader.vert + ${PHYSICS_REPO_ROOT}/Assets/Shaders/TriangleDepthVertexShader.vert + ${PHYSICS_REPO_ROOT}/Assets/Shaders/TriangleVertexShader.vert + ${PHYSICS_REPO_ROOT}/Assets/Shaders/UIVertexShader.vert + ${PHYSICS_REPO_ROOT}/Assets/Shaders/FontPixelShader.frag + ${PHYSICS_REPO_ROOT}/Assets/Shaders/LinePixelShader.frag + ${PHYSICS_REPO_ROOT}/Assets/Shaders/TriangleDepthPixelShader.frag + ${PHYSICS_REPO_ROOT}/Assets/Shaders/TrianglePixelShader.frag + ${PHYSICS_REPO_ROOT}/Assets/Shaders/UIPixelShader.frag + ${PHYSICS_REPO_ROOT}/Assets/Shaders/UIPixelShaderUntextured.frag + ) + + # Compile GLSL shaders + foreach(SHADER ${TEST_FRAMEWORK_GLSL_SHADERS}) + set(SPV_SHADER ${SHADER}.spv) + add_custom_command(OUTPUT ${SPV_SHADER} + COMMAND ${Vulkan_GLSLC_EXECUTABLE} ${SHADER} -o ${SPV_SHADER} + DEPENDS ${SHADER} + COMMENT "Compiling ${SHADER}") + list(APPEND TEST_FRAMEWORK_SPV_SHADERS ${SPV_SHADER}) + endforeach() +endif() + +# Group source files +source_group(TREE ${TEST_FRAMEWORK_ROOT} FILES ${TEST_FRAMEWORK_SRC_FILES}) # Group shader files -source_group(TREE ${PHYSICS_REPO_ROOT} FILES ${TEST_FRAMEWORK_SRC_FILES_SHADERS}) +source_group(TREE ${PHYSICS_REPO_ROOT} FILES ${TEST_FRAMEWORK_SRC_FILES_SHADERS} ${TEST_FRAMEWORK_GLSL_SHADERS} ${TEST_FRAMEWORK_SPV_SHADERS}) # Create TestFramework lib -add_library(TestFramework STATIC ${TEST_FRAMEWORK_SRC_FILES} ${TEST_FRAMEWORK_SRC_FILES_SHADERS}) +add_library(TestFramework STATIC ${TEST_FRAMEWORK_SRC_FILES} ${TEST_FRAMEWORK_SRC_FILES_SHADERS} ${TEST_FRAMEWORK_SPV_SHADERS}) target_include_directories(TestFramework PUBLIC ${TEST_FRAMEWORK_ROOT}) target_link_libraries(TestFramework LINK_PUBLIC Jolt dxguid.lib dinput8.lib dxgi.lib d3d12.lib d3dcompiler.lib) target_precompile_headers(TestFramework PUBLIC ${TEST_FRAMEWORK_ROOT}/TestFramework.h) + +# Enable Vulkan for the TestFramework +if (Vulkan_FOUND) + target_include_directories(TestFramework PUBLIC ${Vulkan_INCLUDE_DIRS}) + target_link_libraries(TestFramework LINK_PUBLIC Jolt ${Vulkan_LIBRARIES}) + if (JPH_ENABLE_VULKAN) + target_compile_definitions(TestFramework PRIVATE JPH_ENABLE_VULKAN) + endif() +endif() diff --git a/TestFramework/UI/UIManager.cpp b/TestFramework/UI/UIManager.cpp index 156d38c67..a1c740569 100644 --- a/TestFramework/UI/UIManager.cpp +++ b/TestFramework/UI/UIManager.cpp @@ -29,22 +29,22 @@ UIManager::UIManager(Renderer *inRenderer) : SetHeight(mRenderer->GetWindowHeight()); // Create input layout - const D3D12_INPUT_ELEMENT_DESC vertex_desc[] = + const PipelineState::EInputDescription vertex_desc[] = { - { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, - { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, - { "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, 20, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, + PipelineState::EInputDescription::Position, + PipelineState::EInputDescription::TexCoord, + PipelineState::EInputDescription::Color }; // Load vertex shader - ComPtr vtx = mRenderer->CreateVertexShader("Assets/Shaders/UIVertexShader.hlsl"); + Ref vtx = mRenderer->CreateVertexShader("Assets/Shaders/UIVertexShader"); // Load pixel shader - ComPtr pix_textured = mRenderer->CreatePixelShader("Assets/Shaders/UIPixelShader.hlsl"); - ComPtr pix_untextured = mRenderer->CreatePixelShader("Assets/Shaders/UIPixelShaderUntextured.hlsl"); + Ref pix_textured = mRenderer->CreatePixelShader("Assets/Shaders/UIPixelShader"); + Ref pix_untextured = mRenderer->CreatePixelShader("Assets/Shaders/UIPixelShaderUntextured"); - mTextured = mRenderer->CreatePipelineState(vtx.Get(), vertex_desc, ARRAYSIZE(vertex_desc), pix_textured.Get(), D3D12_FILL_MODE_SOLID, D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE, PipelineState::EDepthTest::Off, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface); - mUntextured = mRenderer->CreatePipelineState(vtx.Get(), vertex_desc, ARRAYSIZE(vertex_desc), pix_untextured.Get(), D3D12_FILL_MODE_SOLID, D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE, PipelineState::EDepthTest::Off, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface); + mTextured = mRenderer->CreatePipelineState(vtx, vertex_desc, std::size(vertex_desc), pix_textured, PipelineState::EDrawPass::Normal, PipelineState::EFillMode::Solid, PipelineState::ETopology::Triangle, PipelineState::EDepthTest::Off, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface); + mUntextured = mRenderer->CreatePipelineState(vtx, vertex_desc, std::size(vertex_desc), pix_untextured, PipelineState::EDrawPass::Normal, PipelineState::EFillMode::Solid, PipelineState::ETopology::Triangle, PipelineState::EDepthTest::Off, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface); } UIManager::~UIManager() @@ -264,8 +264,8 @@ void UIManager::DrawQuad(int inX, int inY, int inWidth, int inHeight, const UITe { bool has_inner = inQuad.HasInnerPart(); - RenderPrimitive primitive(mRenderer, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); - primitive.CreateVertexBuffer(has_inner? 9 * 6 : 6, sizeof(QuadVertex)); + Ref primitive = mRenderer->CreateRenderPrimitive(PipelineState::ETopology::Triangle); + primitive->CreateVertexBuffer(has_inner? 9 * 6 : 6, sizeof(QuadVertex)); float w = float(inQuad.mTexture->GetWidth()), h = float(inQuad.mTexture->GetHeight()); @@ -277,7 +277,7 @@ void UIManager::DrawQuad(int inX, int inY, int inWidth, int inHeight, const UITe tx1 /= w; ty1 /= h; tx2 /= w; ty2 /= h; - QuadVertex *v = (QuadVertex *)primitive.LockVertexBuffer(); + QuadVertex *v = (QuadVertex *)primitive->LockVertexBuffer(); if (has_inner) { @@ -312,20 +312,20 @@ void UIManager::DrawQuad(int inX, int inY, int inWidth, int inHeight, const UITe sDrawQuad(v, x1, y1, x2, y2, tx1, ty1, tx2, ty2, inColor); } - primitive.UnlockVertexBuffer(); - inQuad.mTexture->Bind(2); + primitive->UnlockVertexBuffer(); + inQuad.mTexture->Bind(); mTextured->Activate(); - primitive.Draw(); + primitive->Draw(); } else { - RenderPrimitive primitive(mRenderer, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); - primitive.CreateVertexBuffer(6, sizeof(QuadVertex)); - QuadVertex *v = (QuadVertex *)primitive.LockVertexBuffer(); + Ref primitive = mRenderer->CreateRenderPrimitive(PipelineState::ETopology::Triangle); + primitive->CreateVertexBuffer(6, sizeof(QuadVertex)); + QuadVertex *v = (QuadVertex *)primitive->LockVertexBuffer(); sDrawQuad(v, x1, y1, x2, y2, 0, 0, 0, 0, inColor); - primitive.UnlockVertexBuffer(); + primitive->UnlockVertexBuffer(); mUntextured->Activate(); - primitive.Draw(); + primitive->Draw(); } } diff --git a/TestFramework/UI/UIManager.h b/TestFramework/UI/UIManager.h index 183c26dad..6d82ba9f6 100644 --- a/TestFramework/UI/UIManager.h +++ b/TestFramework/UI/UIManager.h @@ -6,6 +6,7 @@ #include #include +#include #include #include