From f04c516d2bb87daa9dffd8b361d4b51991ace484 Mon Sep 17 00:00:00 2001 From: bailehuni Date: Sat, 23 Mar 2024 21:16:30 +0000 Subject: [PATCH] Begin adding shadow mapping and a few vulkan fixes --- include/gfx.h | 2 +- include/renderer.h | 11 +- res/engine/shaders/fancy.frag | 10 ++ res/engine/shaders/fancy.vert | 4 + res/engine/shaders/skybox.vert | 1 + src/gfx_device_vulkan.cpp | 230 ++++++++++++++++++++----------- src/renderer.cpp | 106 +++++++++----- src/resources/shader.cpp | 2 +- src/util/gltf_loader.cpp | 12 +- src/vulkan/swapchain.cpp | 6 +- test/res/textures/shadow_map.png | Bin 0 -> 676905 bytes test/src/game.cpp | 2 +- 12 files changed, 254 insertions(+), 132 deletions(-) create mode 100644 test/res/textures/shadow_map.png diff --git a/include/gfx.h b/include/gfx.h index 41f1c80..da4ba3e 100644 --- a/include/gfx.h +++ b/include/gfx.h @@ -119,8 +119,8 @@ struct PipelineInfo { std::string vert_shader_path; std::string frag_shader_path; VertexFormat vertex_format; + CullMode face_cull_mode; bool alpha_blending; - bool backface_culling; bool write_z; bool line_primitives; // false for triangles, true for lines std::vector descriptor_set_layouts; diff --git a/include/renderer.h b/include/renderer.h index 33237a9..07e2a38 100644 --- a/include/renderer.h +++ b/include/renderer.h @@ -85,8 +85,12 @@ class Renderer : private ApplicationComponent { */ // in vertex shader - UniformDescriptor global_uniform; // rarely updates; set 0 - UniformDescriptor frame_uniform; // updates once per frame; set 1 + struct GlobalUniformData { + glm::mat4 proj; + glm::mat4 lightSpaceMatrix; + }; + UniformDescriptor global_uniform; // rarely updates; set 0 binding 0 + UniformDescriptor frame_uniform; // updates once per frame; set 1 binding 0 // in fragment shader const gfx::DescriptorSetLayout* material_set_layout; // set 2; set bound per material @@ -106,6 +110,9 @@ class Renderer : private ApplicationComponent { const gfx::Pipeline* skybox_pipeline = nullptr; const gfx::Buffer* skybox_buffer = nullptr; + gfx::Image* shadow_map = nullptr; + const gfx::Sampler* shadow_map_sampler = nullptr; + void DrawRenderList(gfx::DrawBuffer* draw_buffer, const RenderList& render_list); }; diff --git a/res/engine/shaders/fancy.frag b/res/engine/shaders/fancy.frag index abea1ac..0510c20 100644 --- a/res/engine/shaders/fancy.frag +++ b/res/engine/shaders/fancy.frag @@ -4,6 +4,7 @@ #define PI_INV 0.31830988618379067153776752674503 layout(set = 0, binding = 1) uniform samplerCube globalSetSkybox; +layout(set = 0, binding = 2) uniform sampler2D globalSetShadowmap; layout(set = 2, binding = 0) uniform sampler2D materialSetAlbedoSampler; layout(set = 2, binding = 1) uniform sampler2D materialSetNormalSampler; @@ -16,6 +17,7 @@ layout(location = 3) in vec3 fragLightPosTangentSpace; layout(location = 4) in vec3 fragNormWorldSpace; layout(location = 5) in vec3 fragViewPosWorldSpace; layout(location = 6) in vec3 fragPosWorldSpace; +layout(location = 7) in vec4 fragPosLightSpace; layout(location = 0) out vec4 outColor; @@ -81,5 +83,13 @@ void main() { vec3 ambient_light = vec3(0.09082, 0.13281, 0.18164); lighting += mix(ambient_light, texture(globalSetSkybox, R).rgb, metallic) * ao * diffuse_brdf; // this is NOT physically-based, it just looks cool + // perform perspective divide + vec3 projCoords = fragPosLightSpace.xyz; + projCoords.x = projCoords.x * 0.5 + 0.5; + projCoords.y = projCoords.y * 0.5 + 0.5; + float closestDepth = texture(globalSetShadowmap, projCoords.xy).r; + float currentDepth = projCoords.z; + float shadow = currentDepth > closestDepth ? 1.0 : 0.0; outColor = vec4(min(emission + lighting, 1.0), 1.0); + //outColor = vec4(shadow, 0.0, 0.0, 1.0); } diff --git a/res/engine/shaders/fancy.vert b/res/engine/shaders/fancy.vert index fcb99d2..5fadbc5 100644 --- a/res/engine/shaders/fancy.vert +++ b/res/engine/shaders/fancy.vert @@ -2,6 +2,7 @@ layout(set = 0, binding = 0) uniform GlobalSetUniformBuffer { mat4 proj; + mat4 lightSpaceMatrix; } globalSetUniformBuffer; layout(set = 1, binding = 0) uniform FrameSetUniformBuffer { @@ -24,6 +25,7 @@ layout(location = 3) out vec3 fragLightPosTangentSpace; layout(location = 4) out vec3 fragNormWorldSpace; layout(location = 5) out vec3 fragViewPosWorldSpace; layout(location = 6) out vec3 fragPosWorldSpace; +layout(location = 7) out vec4 fragPosLightSpace; void main() { vec4 worldPosition = constants.model * vec4(inPosition, 1.0); @@ -43,5 +45,7 @@ void main() { fragViewPosWorldSpace = vec3(inverse(frameSetUniformBuffer.view) * vec4(0.0, 0.0, 0.0, 1.0)); fragPosWorldSpace = worldPosition.xyz; + fragPosLightSpace = globalSetUniformBuffer.lightSpaceMatrix * vec4(worldPosition.xyz, 1.0); + gl_Position.y *= -1.0; } diff --git a/res/engine/shaders/skybox.vert b/res/engine/shaders/skybox.vert index d8cda9b..e509a4e 100644 --- a/res/engine/shaders/skybox.vert +++ b/res/engine/shaders/skybox.vert @@ -2,6 +2,7 @@ layout(set = 0, binding = 0) uniform GlobalSetUniformBuffer { mat4 proj; + mat4 lightSpaceMatrix; } globalSetUniformBuffer; layout(set = 1, binding = 0) uniform FrameSetUniformBuffer { diff --git a/src/gfx_device_vulkan.cpp b/src/gfx_device_vulkan.cpp index de6a6fa..d8cf1cd 100644 --- a/src/gfx_device_vulkan.cpp +++ b/src/gfx_device_vulkan.cpp @@ -176,12 +176,12 @@ static VkBufferUsageFlagBits getBufferUsageFlag(gfx::BufferType type) [[maybe_unused]] static VkSamplerAddressMode getSamplerAddressMode(gfx::WrapMode mode) { switch (mode) { - case gfx::WrapMode::kRepeat: - return VK_SAMPLER_ADDRESS_MODE_REPEAT; - case gfx::WrapMode::kMirroredRepeat: - return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; - case gfx::WrapMode::kClampToEdge: - return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + case gfx::WrapMode::kRepeat: + return VK_SAMPLER_ADDRESS_MODE_REPEAT; + case gfx::WrapMode::kMirroredRepeat: + return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; + case gfx::WrapMode::kClampToEdge: + return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; } throw std::runtime_error("Unknown wrap mode"); } @@ -189,10 +189,10 @@ static VkBufferUsageFlagBits getBufferUsageFlag(gfx::BufferType type) [[maybe_unused]] static VkSamplerMipmapMode getSamplerMipmapMode(gfx::Filter filter) { switch (filter) { - case gfx::Filter::kLinear: - return VK_SAMPLER_MIPMAP_MODE_LINEAR; - case gfx::Filter::kNearest: - return VK_SAMPLER_MIPMAP_MODE_NEAREST; + case gfx::Filter::kLinear: + return VK_SAMPLER_MIPMAP_MODE_LINEAR; + case gfx::Filter::kNearest: + return VK_SAMPLER_MIPMAP_MODE_NEAREST; } throw std::runtime_error("Unknown filter"); } @@ -235,7 +235,7 @@ static VkShaderStageFlags getShaderStageFlags(gfx::ShaderStageFlags::Flags flags return out; } -[[maybe_unused]] static VkCullModeFlags getCullModeFlags(gfx::CullMode mode) +static VkCullModeFlags getCullModeFlags(gfx::CullMode mode) { switch (mode) { case gfx::CullMode::kCullNone: @@ -254,12 +254,12 @@ static VkShaderStageFlags getShaderStageFlags(gfx::ShaderStageFlags::Flags flags static VkFormat getImageFormat(gfx::ImageFormat format) { switch (format) { - case gfx::ImageFormat::kLinear: - return VK_FORMAT_R8G8B8A8_UNORM; - case gfx::ImageFormat::kSRGB: - return VK_FORMAT_R8G8B8A8_SRGB; - default: - throw std::runtime_error("Unknown image format"); + case gfx::ImageFormat::kLinear: + return VK_FORMAT_R8G8B8A8_UNORM; + case gfx::ImageFormat::kSRGB: + return VK_FORMAT_R8G8B8A8_SRGB; + default: + throw std::runtime_error("Unknown image format"); } } @@ -646,7 +646,7 @@ gfx::DrawBuffer* GFXDevice::BeginRender() VKCHECK(res); /* perform any pending uniform buffer writes */ - VKCHECK(vkResetCommandPool(pimpl->device.device, frameData.transferPool, 0)); + VKCHECK(vkResetCommandPool(pimpl->device.device, frameData.transferPool, 0)); // TODO: possibly delete this VkCommandBufferBeginInfo transferBeginInfo{ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, @@ -658,36 +658,64 @@ gfx::DrawBuffer* GFXDevice::BeginRender() // transfer cmds... - std::vector barriers{}; - for (gfx::UniformBuffer* uniformBuffer : pimpl->write_queues[currentFrameIndex].uniform_buffer_writes) { - VkBufferCopy copyRegion{}; - copyRegion.srcOffset = 0; - copyRegion.dstOffset = 0; - copyRegion.size = uniformBuffer->stagingBuffer.size; - vkCmdCopyBuffer(frameData.transferBuf, uniformBuffer->stagingBuffer.buffer, uniformBuffer->gpuBuffers[currentFrameIndex].buffer, 1, ©Region); - - VkBufferMemoryBarrier2& barrier = barriers.emplace_back(); - barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2; - barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; - barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; - barrier.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; - barrier.dstAccessMask = 0; - barrier.srcQueueFamilyIndex = pimpl->device.queues.transferQueueFamily; - barrier.dstQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily; - barrier.buffer = uniformBuffer->gpuBuffers[currentFrameIndex].buffer; - barrier.offset = 0; - barrier.size = uniformBuffer->gpuBuffers[currentFrameIndex].size; + if (pimpl->FRAMECOUNT >= FRAMES_IN_FLIGHT) { // cannot (and don't need to) do ownership transfer on first frame[s] + // acquire ownership of buffers + std::vector acquireBarriers{}; + for (gfx::UniformBuffer* uniformBuffer : pimpl->write_queues[currentFrameIndex].uniform_buffer_writes) { + VkBufferMemoryBarrier2& barrier = acquireBarriers.emplace_back(); + barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2; + barrier.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT; + barrier.srcAccessMask = 0; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_COPY_BIT; + barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + barrier.srcQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily; + barrier.dstQueueFamilyIndex = pimpl->device.queues.transferQueueFamily; + barrier.buffer = uniformBuffer->gpuBuffers[currentFrameIndex].buffer; + barrier.offset = 0; + barrier.size = uniformBuffer->gpuBuffers[currentFrameIndex].size; + } + VkDependencyInfo dependencyInfo{}; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.bufferMemoryBarrierCount = (uint32_t)acquireBarriers.size(); + dependencyInfo.pBufferMemoryBarriers = acquireBarriers.data(); + vkCmdPipelineBarrier2(frameData.transferBuf, &dependencyInfo); } - pimpl->write_queues[currentFrameIndex].uniform_buffer_writes.clear(); - VkDependencyInfo dependencyInfo{}; - dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; - dependencyInfo.bufferMemoryBarrierCount = (uint32_t)barriers.size(); - dependencyInfo.pBufferMemoryBarriers = barriers.data(); - vkCmdPipelineBarrier2(frameData.transferBuf, &dependencyInfo); + { + // copy stagings buffers to GPU buffer + for (gfx::UniformBuffer* uniformBuffer : pimpl->write_queues[currentFrameIndex].uniform_buffer_writes) { + VkBufferCopy copyRegion{}; + copyRegion.srcOffset = 0; + copyRegion.dstOffset = 0; + copyRegion.size = uniformBuffer->stagingBuffer.size; + vkCmdCopyBuffer(frameData.transferBuf, uniformBuffer->stagingBuffer.buffer, uniformBuffer->gpuBuffers[currentFrameIndex].buffer, 1, ©Region); + } + } + + { + // release buffers to graphics queue + std::vector releaseBarriers{}; + for (gfx::UniformBuffer* uniformBuffer : pimpl->write_queues[currentFrameIndex].uniform_buffer_writes) { + VkBufferMemoryBarrier2& barrier = releaseBarriers.emplace_back(); + barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2; + barrier.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT; + barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_COPY_BIT; + barrier.dstAccessMask = 0; + barrier.srcQueueFamilyIndex = pimpl->device.queues.transferQueueFamily; + barrier.dstQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily; + barrier.buffer = uniformBuffer->gpuBuffers[currentFrameIndex].buffer; + barrier.offset = 0; + barrier.size = uniformBuffer->gpuBuffers[currentFrameIndex].size; + } + VkDependencyInfo dependencyInfo{}; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.bufferMemoryBarrierCount = (uint32_t)releaseBarriers.size(); + dependencyInfo.pBufferMemoryBarriers = releaseBarriers.data(); + vkCmdPipelineBarrier2(frameData.transferBuf, &dependencyInfo); + } VKCHECK(vkEndCommandBuffer(frameData.transferBuf)); - VkSubmitInfo transferSubmitInfo{ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .pNext = nullptr, @@ -733,13 +761,25 @@ gfx::DrawBuffer* GFXDevice::BeginRender() { // RECORDING - /* change barriers to perform a queue ownership acquire operation */ - for (VkBufferMemoryBarrier2& barrier : barriers) { + // acquire ownership of buffers + std::vector acquireBarriers{}; + for (gfx::UniformBuffer* uniformBuffer : pimpl->write_queues[currentFrameIndex].uniform_buffer_writes) { + VkBufferMemoryBarrier2& barrier = acquireBarriers.emplace_back(); + barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2; barrier.srcStageMask = VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT; barrier.srcAccessMask = 0; barrier.dstStageMask = VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT; barrier.dstAccessMask = VK_ACCESS_2_UNIFORM_READ_BIT; + barrier.srcQueueFamilyIndex = pimpl->device.queues.transferQueueFamily; + barrier.dstQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily; + barrier.buffer = uniformBuffer->gpuBuffers[currentFrameIndex].buffer; + barrier.offset = 0; + barrier.size = uniformBuffer->gpuBuffers[currentFrameIndex].size; } + VkDependencyInfo dependencyInfo{}; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.bufferMemoryBarrierCount = (uint32_t)acquireBarriers.size(); + dependencyInfo.pBufferMemoryBarriers = acquireBarriers.data(); vkCmdPipelineBarrier2(frameData.drawBuf, &dependencyInfo); std::array clearValues{}; // Using same value for all components enables @@ -801,6 +841,27 @@ void GFXDevice::FinishRender(gfx::DrawBuffer* drawBuffer) vkCmdEndRenderPass(drawBuffer->frameData.drawBuf); + // transfer ownership of uniform buffers back to transfer queue + std::vector releaseBarriers{}; + for (gfx::UniformBuffer* uniformBuffer : pimpl->write_queues[drawBuffer->currentFrameIndex].uniform_buffer_writes) { + VkBufferMemoryBarrier2& barrier = releaseBarriers.emplace_back(); + barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2; + barrier.srcStageMask = VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT; + barrier.srcAccessMask = VK_ACCESS_2_UNIFORM_READ_BIT; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT; + barrier.dstAccessMask = 0; + barrier.srcQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily; + barrier.dstQueueFamilyIndex = pimpl->device.queues.transferQueueFamily; + barrier.buffer = uniformBuffer->gpuBuffers[drawBuffer->currentFrameIndex].buffer; + barrier.offset = 0; + barrier.size = uniformBuffer->gpuBuffers[drawBuffer->currentFrameIndex].size; + } + VkDependencyInfo dependencyInfo{}; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.bufferMemoryBarrierCount = (uint32_t)releaseBarriers.size(); + dependencyInfo.pBufferMemoryBarriers = releaseBarriers.data(); + vkCmdPipelineBarrier2(drawBuffer->frameData.drawBuf, &dependencyInfo); + res = vkEndCommandBuffer(drawBuffer->frameData.drawBuf); VKCHECK(res); @@ -846,6 +907,9 @@ void GFXDevice::FinishRender(gfx::DrawBuffer* drawBuffer) else if (res != VK_SUCCESS) throw std::runtime_error("Failed to queue present! Code: " + std::to_string(res)); + // clear write queue + pimpl->write_queues[drawBuffer->currentFrameIndex].uniform_buffer_writes.clear(); + pimpl->FRAMECOUNT++; delete drawBuffer; @@ -1011,12 +1075,7 @@ gfx::Pipeline* GFXDevice::CreatePipeline(const gfx::PipelineInfo& info) rasterizer.rasterizerDiscardEnable = VK_FALSE; // enabling this will not run the fragment shaders at all rasterizer.polygonMode = VK_POLYGON_MODE_FILL; rasterizer.lineWidth = 1.0f; - if (info.backface_culling == true) { - rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; - } - else { - rasterizer.cullMode = VK_CULL_MODE_NONE; - } + rasterizer.cullMode = converters::getCullModeFlags(info.face_cull_mode); rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; rasterizer.depthBiasEnable = VK_FALSE; rasterizer.depthBiasConstantFactor = 0.0f; // ignored @@ -1488,8 +1547,8 @@ gfx::Image* GFXDevice::CreateImage(uint32_t w, uint32_t h, gfx::ImageFormat inpu beforeCopyBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; beforeCopyBarrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE; beforeCopyBarrier.srcAccessMask = VK_ACCESS_2_NONE; - beforeCopyBarrier.dstStageMask = VK_PIPELINE_STAGE_2_COPY_BIT; - beforeCopyBarrier.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + beforeCopyBarrier.dstStageMask = VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_BLIT_BIT; // all subresources will be COPYed or BLITed to next + beforeCopyBarrier.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; // Image must be TRANSFER_DST_OPTIMAL before either stage performs a TRANSFER_WRITE beforeCopyBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; beforeCopyBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; beforeCopyBarrier.srcQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily; @@ -1528,10 +1587,11 @@ gfx::Image* GFXDevice::CreateImage(uint32_t w, uint32_t h, gfx::ImageFormat inpu // Must happen after TRANSFER_WRITE in the COPY stage and BLIT stage. VkImageMemoryBarrier2 beforeBlitBarrier{}; beforeBlitBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; - beforeBlitBarrier.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_BLIT_BIT; - beforeBlitBarrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; - beforeBlitBarrier.dstStageMask = VK_PIPELINE_STAGE_2_BLIT_BIT; - beforeBlitBarrier.dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT; + beforeBlitBarrier.srcStageMask = + VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_BLIT_BIT; // previous mip level was either just COPYed to or just BLITed to + beforeBlitBarrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; // these actions were TRANSFER_WRITEs + beforeBlitBarrier.dstStageMask = VK_PIPELINE_STAGE_2_BLIT_BIT; // the image will be BLITed from next + beforeBlitBarrier.dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT; // this is a TRANSFER_READ beforeBlitBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; beforeBlitBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; beforeBlitBarrier.srcQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily; @@ -1570,9 +1630,9 @@ gfx::Image* GFXDevice::CreateImage(uint32_t w, uint32_t h, gfx::ImageFormat inpu VkImageMemoryBarrier2 afterBlitBarrier{}; afterBlitBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; afterBlitBarrier.srcStageMask = VK_PIPELINE_STAGE_2_BLIT_BIT; - afterBlitBarrier.srcAccessMask = 0; + afterBlitBarrier.srcAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT; // previous mip level was just READ from in a BLIT afterBlitBarrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; - afterBlitBarrier.dstAccessMask = VK_ACCESS_2_INPUT_ATTACHMENT_READ_BIT | VK_ACCESS_2_SHADER_READ_BIT; + afterBlitBarrier.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT; // it will next be sampled in a frag shader afterBlitBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; afterBlitBarrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; afterBlitBarrier.srcQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily; @@ -1597,10 +1657,17 @@ gfx::Image* GFXDevice::CreateImage(uint32_t w, uint32_t h, gfx::ImageFormat inpu // barrier: (mipLevels - 1) TRANSFER_DST_OPTIMAL -> SHADER_READ_ONLY_OPTIMAL VkImageMemoryBarrier2 finalBlitBarrier{}; finalBlitBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; - finalBlitBarrier.srcStageMask = VK_PIPELINE_STAGE_2_BLIT_BIT | VK_PIPELINE_STAGE_2_COPY_BIT; + if (mipLevels == 1) { + // if no mipmaps were generated, the image was just COPYed to with a WRITE + finalBlitBarrier.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT; // the final mip level was BLITed to with a WRITE + } + else { + // mips were generated, therefore last mip level was just BLITed to + finalBlitBarrier.srcStageMask = VK_PIPELINE_STAGE_2_BLIT_BIT; + } finalBlitBarrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; finalBlitBarrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; - finalBlitBarrier.dstAccessMask = VK_ACCESS_2_INPUT_ATTACHMENT_READ_BIT | VK_ACCESS_2_SHADER_READ_BIT; + finalBlitBarrier.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT; finalBlitBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; finalBlitBarrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; finalBlitBarrier.srcQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily; @@ -1649,7 +1716,7 @@ gfx::Image* GFXDevice::CreateImageCubemap(uint32_t w, uint32_t h, gfx::ImageForm if (pimpl->FRAMECOUNT != 0) abort(); // TODO. This is annoying gfx::Image* out = new gfx::Image{}; - + uint32_t mipLevels = static_cast(std::floor(std::log2(std::max(w, h)))) + 1; VkFormat imageFormat = converters::getImageFormat(input_format); @@ -1733,7 +1800,7 @@ gfx::Image* GFXDevice::CreateImageCubemap(uint32_t w, uint32_t h, gfx::ImageForm beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; VKCHECK(vkBeginCommandBuffer(commandBuffer, &beginInfo)); - // barrier: (all mip levels): UNDEFINED -> TRANSFER_DST_OPTIMAL + // barrier: (all mip levels, all faces): UNDEFINED -> TRANSFER_DST_OPTIMAL // Used for copying staging buffer AND blitting mipmaps // Must happen before vkCmdCopyBufferToImage performs a TRANSFER_WRITE in // the COPY stage. @@ -1741,7 +1808,8 @@ gfx::Image* GFXDevice::CreateImageCubemap(uint32_t w, uint32_t h, gfx::ImageForm beforeCopyBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; beforeCopyBarrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE; beforeCopyBarrier.srcAccessMask = VK_ACCESS_2_NONE; - beforeCopyBarrier.dstStageMask = VK_PIPELINE_STAGE_2_COPY_BIT; + beforeCopyBarrier.dstStageMask = + VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_BLIT_BIT; // all parts of the image will be either TRANSFERed to or BLITed to next beforeCopyBarrier.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; beforeCopyBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; beforeCopyBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; @@ -1782,10 +1850,12 @@ gfx::Image* GFXDevice::CreateImageCubemap(uint32_t w, uint32_t h, gfx::ImageForm // Must happen after TRANSFER_WRITE in the COPY stage and BLIT stage. VkImageMemoryBarrier2 beforeBlitBarrier{}; beforeBlitBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; - beforeBlitBarrier.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_BLIT_BIT; - beforeBlitBarrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; - beforeBlitBarrier.dstStageMask = VK_PIPELINE_STAGE_2_BLIT_BIT; - beforeBlitBarrier.dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT; + beforeBlitBarrier.srcStageMask = + VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_BLIT_BIT; // subresource was either just copied to or just blitted to + beforeBlitBarrier.srcAccessMask = + VK_ACCESS_2_TRANSFER_WRITE_BIT; // wait until CopyBufferToImage and BlitImage have performed TRANSFER_WRITE on subresource + beforeBlitBarrier.dstStageMask = VK_PIPELINE_STAGE_2_BLIT_BIT; // subresource is going to be blitted from + beforeBlitBarrier.dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT; // subresource will be READ from during the blit beforeBlitBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; beforeBlitBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; beforeBlitBarrier.srcQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily; @@ -1807,16 +1877,16 @@ gfx::Image* GFXDevice::CreateImageCubemap(uint32_t w, uint32_t h, gfx::ImageForm blit.srcSubresource.mipLevel = i - 1; blit.srcSubresource.baseArrayLayer = face; blit.srcSubresource.layerCount = 1; - blit.srcOffsets[0] = { 0, 0, 0 }; - blit.srcOffsets[1] = { mipWidth, mipHeight, 1 }; + blit.srcOffsets[0] = {0, 0, 0}; + blit.srcOffsets[1] = {mipWidth, mipHeight, 1}; blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; blit.dstSubresource.mipLevel = i; blit.dstSubresource.baseArrayLayer = face; blit.dstSubresource.layerCount = 1; - blit.dstOffsets[0] = { 0, 0, 0 }; - blit.dstOffsets[1] = { mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1 }; + blit.dstOffsets[0] = {0, 0, 0}; + blit.dstOffsets[1] = {mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1}; vkCmdBlitImage(commandBuffer, out->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, out->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, - VK_FILTER_LINEAR); + VK_FILTER_LINEAR); // WRITE-AFTER-WRITE error here // barrier: (i - 1) TRANSFER_SRC_OPTIMAL -> SHADER_READ_ONLY_OPTIMALs // Must happen after usage in the BLIT stage. @@ -1824,9 +1894,9 @@ gfx::Image* GFXDevice::CreateImageCubemap(uint32_t w, uint32_t h, gfx::ImageForm VkImageMemoryBarrier2 afterBlitBarrier{}; afterBlitBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; afterBlitBarrier.srcStageMask = VK_PIPELINE_STAGE_2_BLIT_BIT; - afterBlitBarrier.srcAccessMask = 0; + afterBlitBarrier.srcAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT; afterBlitBarrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; - afterBlitBarrier.dstAccessMask = VK_ACCESS_2_INPUT_ATTACHMENT_READ_BIT | VK_ACCESS_2_SHADER_READ_BIT; + afterBlitBarrier.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT; afterBlitBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; afterBlitBarrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; afterBlitBarrier.srcQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily; @@ -1851,10 +1921,10 @@ gfx::Image* GFXDevice::CreateImageCubemap(uint32_t w, uint32_t h, gfx::ImageForm // barrier: (mipLevels - 1) TRANSFER_DST_OPTIMAL -> SHADER_READ_ONLY_OPTIMAL VkImageMemoryBarrier2 finalBlitBarrier{}; finalBlitBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; - finalBlitBarrier.srcStageMask = VK_PIPELINE_STAGE_2_BLIT_BIT | VK_PIPELINE_STAGE_2_COPY_BIT; + finalBlitBarrier.srcStageMask = VK_PIPELINE_STAGE_2_BLIT_BIT; finalBlitBarrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; finalBlitBarrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; - finalBlitBarrier.dstAccessMask = VK_ACCESS_2_INPUT_ATTACHMENT_READ_BIT | VK_ACCESS_2_SHADER_READ_BIT; + finalBlitBarrier.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT; finalBlitBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; finalBlitBarrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; finalBlitBarrier.srcQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily; @@ -1870,7 +1940,6 @@ gfx::Image* GFXDevice::CreateImageCubemap(uint32_t w, uint32_t h, gfx::ImageForm afterBlitDependency.imageMemoryBarrierCount = 1; afterBlitDependency.pImageMemoryBarriers = &finalBlitBarrier; vkCmdPipelineBarrier2(commandBuffer, &afterBlitDependency); - } VKCHECK(vkEndCommandBuffer(commandBuffer)); @@ -1893,7 +1962,6 @@ gfx::Image* GFXDevice::CreateImageCubemap(uint32_t w, uint32_t h, gfx::ImageForm return out; } - void GFXDevice::DestroyImage(const gfx::Image* image) { assert(image != nullptr); @@ -1905,7 +1973,7 @@ void GFXDevice::DestroyImage(const gfx::Image* image) const gfx::Sampler* GFXDevice::CreateSampler(const gfx::SamplerInfo& info) { gfx::Sampler* out = new gfx::Sampler{}; - + VkSamplerCreateInfo samplerInfo{}; samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; samplerInfo.magFilter = converters::getFilter(info.magnify); diff --git a/src/renderer.cpp b/src/renderer.cpp index bb926fe..7324d27 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -26,13 +26,21 @@ Renderer::Renderer(Application& app, gfx::GraphicsSettings settings) : Applicati auto& binding1 = globalSetBindings.emplace_back(); binding1.descriptor_type = gfx::DescriptorType::kCombinedImageSampler; binding1.stage_flags = gfx::ShaderStageFlags::kFragment; + auto& binding2 = globalSetBindings.emplace_back(); + binding2.descriptor_type = gfx::DescriptorType::kCombinedImageSampler; + binding2.stage_flags = gfx::ShaderStageFlags::kFragment; } global_uniform.layout = device_->CreateDescriptorSetLayout(globalSetBindings); global_uniform.set = device_->AllocateDescriptorSet(global_uniform.layout); - global_uniform.uniform_buffer_data.data = glm::mat4{1.0f}; + global_uniform.uniform_buffer_data.data.proj = glm::mat4{1.0f}; + const glm::vec3 light_location{ -0.4278, 0.7923, 0.43502 }; + const glm::mat4 light_proj = glm::orthoRH_ZO(-32.0f, 32.0f, -32.0f, 32.0f, -100.0f, 100.0f); + const glm::mat4 light_view = glm::lookAtRH(light_location, glm::vec3{ 0.0f, 0.0f, 0.0f }, glm::vec3{ 0.0f, 0.0f, 1.0f }); + global_uniform.uniform_buffer_data.data.lightSpaceMatrix = light_proj * light_view; global_uniform.uniform_buffer = device_->CreateUniformBuffer(sizeof(global_uniform.uniform_buffer_data), &global_uniform.uniform_buffer_data); device_->UpdateDescriptorUniformBuffer(global_uniform.set, 0, global_uniform.uniform_buffer, 0, sizeof(global_uniform.uniform_buffer_data)); // binding1 is updated towards the end of the constructor once the skybox texture is loaded + // binding2 is updated just after that std::vector frameSetBindings; { @@ -68,30 +76,29 @@ Renderer::Renderer(Application& app, gfx::GraphicsSettings settings) : Applicati debug_pipeline_info.frag_shader_path = GetResourcePath("engine/shaders/debug.frag"); debug_pipeline_info.vertex_format = debug_vertex_format; debug_pipeline_info.alpha_blending = false; - debug_pipeline_info.backface_culling = false; // probably ignored for line rendering - debug_pipeline_info.write_z = false; // lines don't need the depth buffer + debug_pipeline_info.face_cull_mode = gfx::CullMode::kCullNone; // probably ignored for line rendering + debug_pipeline_info.write_z = false; // lines don't need the depth buffer // debug_pipeline_info.descriptor_set_layouts = empty; debug_pipeline_info.line_primitives = true; debug_rendering_things_.pipeline = device_->CreatePipeline(debug_pipeline_info); } - // create the skybox cubemap + // create the skybox cubemap and update global skybox combined-image-sampler { constexpr uint32_t cubemap_w = 2048; constexpr uint32_t cubemap_h = 2048; int w{}, h{}; std::array>, 6> face_unq_ptrs{}; std::array face_unsafe_ptrs{}; - + for (int face = 0; face < 6; ++face) { std::string path = std::string("engine/textures/skybox") + std::to_string(face) + std::string(".jpg"); face_unq_ptrs[face] = util::ReadImageFile(GetResourcePath(path), w, h); if (cubemap_w != w || cubemap_h != h) throw std::runtime_error("Skybox textures must be 512x512!"); face_unsafe_ptrs[face] = face_unq_ptrs[face]->data(); } - - + skybox_cubemap = device_->CreateImageCubemap(cubemap_w, cubemap_h, gfx::ImageFormat::kSRGB, face_unsafe_ptrs); gfx::SamplerInfo sampler_info{}; sampler_info.magnify = gfx::Filter::kLinear; @@ -102,6 +109,8 @@ Renderer::Renderer(Application& app, gfx::GraphicsSettings settings) : Applicati sampler_info.wrap_w = gfx::WrapMode::kClampToEdge; sampler_info.anisotropic_filtering = true; skybox_sampler = device_->CreateSampler(sampler_info); + + device_->UpdateDescriptorCombinedImageSampler(global_uniform.set, 1, skybox_cubemap, skybox_sampler); } // create skybox shader @@ -115,15 +124,13 @@ Renderer::Renderer(Application& app, gfx::GraphicsSettings settings) : Applicati pipeline_info.frag_shader_path = GetResourcePath("engine/shaders/skybox.frag"); pipeline_info.vertex_format = vertex_format; pipeline_info.alpha_blending = false; - pipeline_info.backface_culling = true; + pipeline_info.face_cull_mode = gfx::CullMode::kCullBack; pipeline_info.write_z = false; pipeline_info.line_primitives = false; pipeline_info.descriptor_set_layouts.push_back(GetGlobalSetLayout()); pipeline_info.descriptor_set_layouts.push_back(GetFrameSetLayout()); skybox_pipeline = device_->CreatePipeline(pipeline_info); - - device_->UpdateDescriptorCombinedImageSampler(global_uniform.set, 1, skybox_cubemap, skybox_sampler); } // create skybox vertex buffer @@ -136,40 +143,40 @@ Renderer::Renderer(Application& app, gfx::GraphicsSettings settings) : Applicati v.push_back({2.0f, 0.0f, 2.0f}); v.push_back({0.0f, 0.0f, 2.0f}); // back - v.push_back({2.0f, 2.0f, 2.0f }); - v.push_back({ 2.0f, 2.0f, 0.0f}); + v.push_back({2.0f, 2.0f, 2.0f}); + v.push_back({2.0f, 2.0f, 0.0f}); v.push_back({0.0f, 2.0f, 0.0f}); v.push_back({0.0f, 2.0f, 0.0f}); - v.push_back({0.0f, 2.0f, 2.0f }); - v.push_back({ 2.0f, 2.0f, 2.0f }); + v.push_back({0.0f, 2.0f, 2.0f}); + v.push_back({2.0f, 2.0f, 2.0f}); // left - v.push_back({0.0f, 2.0f, 2.0f }); + v.push_back({0.0f, 2.0f, 2.0f}); v.push_back({0.0f, 2.0f, 0.0f}); v.push_back({0.0f, 0.0f, 0.0f}); v.push_back({0.0f, 0.0f, 0.0f}); - v.push_back({0.0f, 0.0f, 2.0f }); - v.push_back({0.0f, 2.0f, 2.0f }); + v.push_back({0.0f, 0.0f, 2.0f}); + v.push_back({0.0f, 2.0f, 2.0f}); // right - v.push_back({ 2.0f, 0.0f, 2.0f }); - v.push_back({ 2.0f, 0.0f, 0.0f}); - v.push_back({ 2.0f, 2.0f, 0.0f}); - v.push_back({ 2.0f, 2.0f, 0.0f}); - v.push_back({ 2.0f, 2.0f, 2.0f }); - v.push_back({ 2.0f, 0.0f, 2.0f }); + v.push_back({2.0f, 0.0f, 2.0f}); + v.push_back({2.0f, 0.0f, 0.0f}); + v.push_back({2.0f, 2.0f, 0.0f}); + v.push_back({2.0f, 2.0f, 0.0f}); + v.push_back({2.0f, 2.0f, 2.0f}); + v.push_back({2.0f, 0.0f, 2.0f}); // top - v.push_back({0.0f, 2.0f, 2.0f }); - v.push_back({0.0f, 0.0f, 2.0f }); - v.push_back({ 2.0f, 0.0f, 2.0f }); - v.push_back({ 2.0f, 0.0f, 2.0f }); - v.push_back({ 2.0f, 2.0f, 2.0f }); - v.push_back({0.0f, 2.0f, 2.0f }); + v.push_back({0.0f, 2.0f, 2.0f}); + v.push_back({0.0f, 0.0f, 2.0f}); + v.push_back({2.0f, 0.0f, 2.0f}); + v.push_back({2.0f, 0.0f, 2.0f}); + v.push_back({2.0f, 2.0f, 2.0f}); + v.push_back({0.0f, 2.0f, 2.0f}); // bottom - v.push_back({ 2.0f, 2.0f, 0.0f}); - v.push_back({ 2.0f, 0.0f, 0.0f}); + v.push_back({2.0f, 2.0f, 0.0f}); + v.push_back({2.0f, 0.0f, 0.0f}); v.push_back({0.0f, 0.0f, 0.0f}); v.push_back({0.0f, 0.0f, 0.0f}); v.push_back({0.0f, 2.0f, 0.0f}); - v.push_back({ 2.0f, 2.0f, 0.0f}); + v.push_back({2.0f, 2.0f, 0.0f}); for (glm::vec3& pos : v) { pos.x -= 1.0f; pos.y -= 1.0f; @@ -181,10 +188,31 @@ Renderer::Renderer(Application& app, gfx::GraphicsSettings settings) : Applicati skybox_buffer = device_->CreateBuffer(gfx::BufferType::kVertex, v.size() * sizeof(glm::vec3), v.data()); } + + // shadow mapping... + { + int w{}, h{}; + auto shadowmap_image = util::ReadImageFile(GetResourcePath("textures/shadow_map.png"), w, h); + shadow_map = device_->CreateImage(w, h, gfx::ImageFormat::kLinear, shadowmap_image->data()); + gfx::SamplerInfo sampler_info{}; + sampler_info.magnify = gfx::Filter::kLinear; + sampler_info.minify = gfx::Filter::kLinear; + sampler_info.mipmap = gfx::Filter::kLinear; // trilinear is apparently good for shadow maps + sampler_info.wrap_u = gfx::WrapMode::kClampToEdge; + sampler_info.wrap_v = gfx::WrapMode::kClampToEdge; + sampler_info.wrap_w = gfx::WrapMode::kClampToEdge; + sampler_info.anisotropic_filtering = false; // Copilot says not to use aniso for shadow maps + shadow_map_sampler = device_->CreateSampler(sampler_info); + + device_->UpdateDescriptorCombinedImageSampler(global_uniform.set, 2, shadow_map, shadow_map_sampler); + } }; Renderer::~Renderer() { + device_->DestroySampler(shadow_map_sampler); + device_->DestroyImage(shadow_map); + device_->DestroyBuffer(skybox_buffer); device_->DestroyPipeline(skybox_pipeline); device_->DestroySampler(skybox_sampler); @@ -213,15 +241,19 @@ void Renderer::PreRender(bool window_is_resized, glm::mat4 camera_transform) const glm::mat4 proj_matrix = glm::perspectiveRH_ZO(camera_settings_.vertical_fov_radians, viewport_aspect_ratio_, camera_settings_.clip_near, camera_settings_.clip_far); /* update SET 0 (rarely changing uniforms)*/ - global_uniform.uniform_buffer_data.data = proj_matrix; + global_uniform.uniform_buffer_data.data.proj = proj_matrix; device_->WriteUniformBuffer(global_uniform.uniform_buffer, 0, sizeof(global_uniform.uniform_buffer_data), &global_uniform.uniform_buffer_data); } - // set camera view matrix uniform - /* update SET 1 (per frame uniforms) */ const glm::mat4 view_matrix = glm::inverse(camera_transform); frame_uniform.uniform_buffer_data.data = view_matrix; device_->WriteUniformBuffer(frame_uniform.uniform_buffer, 0, sizeof(frame_uniform.uniform_buffer_data), &frame_uniform.uniform_buffer_data); + + // override with light matrix + frame_uniform.uniform_buffer_data.data = glm::mat4{1.0f}; + device_->WriteUniformBuffer(frame_uniform.uniform_buffer, 0, sizeof(frame_uniform.uniform_buffer_data), &frame_uniform.uniform_buffer_data); + global_uniform.uniform_buffer_data.data.proj = global_uniform.uniform_buffer_data.data.lightSpaceMatrix; + device_->WriteUniformBuffer(global_uniform.uniform_buffer, 0, sizeof(global_uniform.uniform_buffer_data), &global_uniform.uniform_buffer_data); } void Renderer::Render(const RenderList* static_list, const RenderList* dynamic_list, const std::vector& debug_lines) @@ -260,8 +292,8 @@ void Renderer::Render(const RenderList* static_list, const RenderList* dynamic_l device_->CmdBindPipeline(draw_buffer, debug_rendering_things_.pipeline); DebugPush push{}; for (const Line& l : debug_lines) { - push.pos1 = global_uniform.uniform_buffer_data.data * frame_uniform.uniform_buffer_data.data * glm::vec4(l.pos1, 1.0f); - push.pos2 = global_uniform.uniform_buffer_data.data * frame_uniform.uniform_buffer_data.data * glm::vec4(l.pos2, 1.0f); + push.pos1 = global_uniform.uniform_buffer_data.data.proj * frame_uniform.uniform_buffer_data.data * glm::vec4(l.pos1, 1.0f); + push.pos2 = global_uniform.uniform_buffer_data.data.proj * frame_uniform.uniform_buffer_data.data * glm::vec4(l.pos2, 1.0f); push.color = l.color; device_->CmdPushConstants(draw_buffer, debug_rendering_things_.pipeline, 0, sizeof(DebugPush), &push); device_->CmdDraw(draw_buffer, 2, 1, 0, 0); diff --git a/src/resources/shader.cpp b/src/resources/shader.cpp index a00e73b..e643505 100644 --- a/src/resources/shader.cpp +++ b/src/resources/shader.cpp @@ -48,7 +48,7 @@ Shader::Shader(Renderer* renderer, const std::string& vertPath, const std::strin info.frag_shader_path = fragPath; info.vertex_format = vertFormat; info.alpha_blending = settings.alpha_blending; - info.backface_culling = settings.cull_backface; + info.face_cull_mode = settings.cull_backface ? gfx::CullMode::kCullBack : gfx::CullMode::kCullNone; info.write_z = settings.write_z; info.line_primitives = false; info.descriptor_set_layouts.push_back(renderer->GetGlobalSetLayout()); diff --git a/src/util/gltf_loader.cpp b/src/util/gltf_loader.cpp index 9271211..c2abc95 100644 --- a/src/util/gltf_loader.cpp +++ b/src/util/gltf_loader.cpp @@ -555,12 +555,12 @@ engine::Entity LoadGLTF(Scene& scene, const std::string& path, bool isStatic) // get AABB AABB box{}; - box.min.x = pos_accessor.minValues.at(0); - box.min.y = pos_accessor.minValues.at(1); - box.min.z = pos_accessor.minValues.at(2); - box.max.x = pos_accessor.maxValues.at(0); - box.max.y = pos_accessor.maxValues.at(1); - box.max.z = pos_accessor.maxValues.at(2); + box.min.x = static_cast(pos_accessor.minValues.at(0)); + box.min.y = static_cast(pos_accessor.minValues.at(1)); + box.min.z = static_cast(pos_accessor.minValues.at(2)); + box.max.x = static_cast(pos_accessor.maxValues.at(0)); + box.max.y = static_cast(pos_accessor.maxValues.at(1)); + box.max.z = static_cast(pos_accessor.maxValues.at(2)); primitive_array.emplace_back(engine_mesh, engine_material, box); } diff --git a/src/vulkan/swapchain.cpp b/src/vulkan/swapchain.cpp index 1ccb65b..058de90 100644 --- a/src/vulkan/swapchain.cpp +++ b/src/vulkan/swapchain.cpp @@ -204,17 +204,17 @@ namespace engine { .srcStageMask = VK_PIPELINE_STAGE_NONE, .dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, .srcAccessMask = 0, - .dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + .dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, .dependencyFlags = 0, }, { // Image Layout Transition .srcSubpass = VK_SUBPASS_EXTERNAL, .dstSubpass = 0, - .srcStageMask = VK_PIPELINE_STAGE_NONE, + .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, .srcAccessMask = 0, - .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, .dependencyFlags = 0, }, }; diff --git a/test/res/textures/shadow_map.png b/test/res/textures/shadow_map.png new file mode 100644 index 0000000000000000000000000000000000000000..d0b92033c551bd5299b623a8d60c4c7db9b42135 GIT binary patch literal 676905 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 b`gvl2=5WaakgsE?u<@ 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

T2r7!RM>^IS{HguGB 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 aVzvF+!(IF$b~y|F8#ZL*=f61%fA~K=Mi+|! literal 0 HcmV?d00001 diff --git a/test/src/game.cpp b/test/src/game.cpp index f790e88..b6ac6f8 100644 --- a/test/src/game.cpp +++ b/test/src/game.cpp @@ -89,7 +89,7 @@ void PlayGame(GameSettings settings) /* floor */ engine::Entity floor = engine::util::LoadGLTF(*main_scene, app.GetResourcePath("models/floor.glb")); - main_scene->GetComponent(main_scene->GetEntity("Cube", floor))->visible = false; + //main_scene->GetComponent(main_scene->GetEntity("Cube", floor))->visible = false; engine::Entity monke = engine::util::LoadGLTF(*main_scene, app.GetResourcePath("models/monke.glb")); main_scene->GetComponent(monke)->position.y += 10.0f;