From bd0305f6ddf48d19cf32f170b9c5925246eed864 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sun, 22 Feb 2026 09:34:27 -0800 Subject: [PATCH] Stabilize Vulkan rendering state for minimap, foliage, and water --- assets/shaders/m2.frag.glsl | 43 ++- assets/shaders/water.frag.glsl | 83 +++++- assets/shaders/water.vert.glsl | 6 +- include/rendering/vk_context.hpp | 18 ++ include/rendering/water_renderer.hpp | 21 ++ src/rendering/m2_renderer.cpp | 56 +++- src/rendering/minimap.cpp | 5 +- src/rendering/renderer.cpp | 18 +- src/rendering/vk_context.cpp | 411 +++++++++++++++++++++------ src/rendering/water_renderer.cpp | 290 ++++++++++++++++++- 10 files changed, 834 insertions(+), 117 deletions(-) diff --git a/assets/shaders/m2.frag.glsl b/assets/shaders/m2.frag.glsl index be93ad29..c1a08392 100644 --- a/assets/shaders/m2.frag.glsl +++ b/assets/shaders/m2.frag.glsl @@ -38,7 +38,26 @@ layout(location = 0) out vec4 outColor; void main() { vec4 texColor = hasTexture != 0 ? texture(uTexture, TexCoord) : vec4(1.0); - if (alphaTest != 0 && texColor.a < 0.5) discard; + float alphaCutoff = 0.5; + if (alphaTest == 2) { + // Vegetation cutout: lower threshold to preserve leaf coverage at grazing angles. + alphaCutoff = 0.33; + } else if (alphaTest != 0) { + alphaCutoff = 0.35; + } + if (alphaTest == 2) { + float alpha = texColor.a; + float softBand = 0.12; + if (alpha < (alphaCutoff - softBand)) discard; + if (alpha < alphaCutoff) { + vec2 p = floor(gl_FragCoord.xy); + float n = fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453); + float keep = clamp((alpha - (alphaCutoff - softBand)) / softBand, 0.0, 1.0); + if (n > keep) discard; + } + } else if (alphaTest != 0 && texColor.a < alphaCutoff) { + discard; + } if (colorKeyBlack != 0) { float lum = dot(texColor.rgb, vec3(0.299, 0.587, 0.114)); if (lum < colorKeyThreshold) discard; @@ -46,10 +65,12 @@ void main() { if (blendMode == 1 && texColor.a < 0.004) discard; vec3 norm = normalize(Normal); - if (!gl_FrontFacing) norm = -norm; + bool foliageTwoSided = (alphaTest == 2); + if (!foliageTwoSided && !gl_FrontFacing) norm = -norm; vec3 ldir = normalize(-lightDir.xyz); - float diff = max(dot(norm, ldir), 0.0); + float nDotL = dot(norm, ldir); + float diff = foliageTwoSided ? abs(nDotL) : max(nDotL, 0.0); vec3 result; if (unlit != 0) { @@ -64,10 +85,11 @@ void main() { vec4 lsPos = lightSpaceMatrix * vec4(FragPos, 1.0); vec3 proj = lsPos.xyz / lsPos.w * 0.5 + 0.5; if (proj.z <= 1.0) { - float bias = max(0.005 * (1.0 - dot(norm, ldir)), 0.001); + float bias = max(0.005 * (1.0 - abs(dot(norm, ldir))), 0.001); shadow = texture(uShadowMap, vec3(proj.xy, proj.z - bias)); } shadow = mix(1.0, shadow, shadowParams.y); + if (foliageTwoSided) shadow = max(shadow, 0.45); } result = ambientColor.rgb * texColor.rgb @@ -82,5 +104,16 @@ void main() { float fogFactor = clamp((fogParams.y - dist) / (fogParams.y - fogParams.x), 0.0, 1.0); result = mix(fogColor.rgb, result, fogFactor); - outColor = vec4(result, texColor.a * fadeAlpha); + float outAlpha = texColor.a * fadeAlpha; + // Cutout materials should not remain partially transparent after discard, + // otherwise foliage cards look view-dependent. + if (alphaTest != 0 || colorKeyBlack != 0) { + outAlpha = fadeAlpha; + } + // Foliage cutout should stay opaque after alpha discard to avoid + // view-angle translucency artifacts. + if (alphaTest == 2) { + outAlpha = 1.0 * fadeAlpha; + } + outColor = vec4(result, outAlpha); } diff --git a/assets/shaders/water.frag.glsl b/assets/shaders/water.frag.glsl index 5bea94c2..acf037f1 100644 --- a/assets/shaders/water.frag.glsl +++ b/assets/shaders/water.frag.glsl @@ -27,37 +27,96 @@ layout(set = 1, binding = 0) uniform WaterMaterial { float alphaScale; }; +layout(set = 2, binding = 0) uniform sampler2D SceneColor; +layout(set = 2, binding = 1) uniform sampler2D SceneDepth; + layout(location = 0) in vec3 FragPos; layout(location = 1) in vec3 Normal; layout(location = 2) in vec2 TexCoord; layout(location = 3) in float WaveOffset; +layout(location = 4) in vec2 ScreenUV; layout(location = 0) out vec4 outColor; +vec3 dualScrollWaveNormal(vec2 p, float time) { + // Two independently scrolling octaves (normal-map style layering). + vec2 d1 = normalize(vec2(0.86, 0.51)); + vec2 d2 = normalize(vec2(-0.47, 0.88)); + float f1 = 0.19; + float f2 = 0.43; + float s1 = 0.95; + float s2 = 1.73; + float a1 = 0.26; + float a2 = 0.12; + + vec2 p1 = p + d1 * (time * s1 * 4.0); + vec2 p2 = p + d2 * (time * s2 * 4.0); + + float ph1 = dot(p1, d1) * f1; + float ph2 = dot(p2, d2) * f2; + + float c1 = cos(ph1); + float c2 = cos(ph2); + + float dHx = c1 * d1.x * f1 * a1 + c2 * d2.x * f2 * a2; + float dHz = c1 * d1.y * f1 * a1 + c2 * d2.y * f2 * a2; + + return normalize(vec3(-dHx, 1.0, -dHz)); +} + void main() { float time = fogParams.z; - vec3 norm = normalize(Normal); + vec3 meshNorm = normalize(Normal); + vec3 waveNorm = dualScrollWaveNormal(FragPos.xz, time); + vec3 norm = normalize(mix(meshNorm, waveNorm, 0.82)); + vec3 viewDir = normalize(viewPos.xyz - FragPos); vec3 ldir = normalize(-lightDir.xyz); + float ndotv = max(dot(norm, viewDir), 0.0); float diff = max(dot(norm, ldir), 0.0); vec3 halfDir = normalize(ldir + viewDir); - float spec = pow(max(dot(norm, halfDir), 0.0), 128.0); + float spec = pow(max(dot(norm, halfDir), 0.0), 96.0); float sparkle = sin(FragPos.x * 20.0 + time * 3.0) * sin(FragPos.z * 20.0 + time * 2.5); sparkle = max(0.0, sparkle) * shimmerStrength; - vec3 color = waterColor.rgb * (ambientColor.rgb + diff * lightColor.rgb) - + spec * lightColor.rgb * 0.5 - + sparkle * lightColor.rgb * 0.3; - float crest = smoothstep(0.3, 1.0, WaveOffset) * 0.15; - color += vec3(crest); - - float fresnel = pow(1.0 - max(dot(norm, viewDir), 0.0), 3.0); - float alpha = mix(waterAlpha * 0.6, waterAlpha, fresnel) * alphaScale; - float dist = length(viewPos.xyz - FragPos); - alpha *= smoothstep(800.0, 200.0, dist); + + // Beer-Lambert style approximation from view distance. + float opticalDepth = 1.0 - exp(-dist * 0.0035); + vec3 litTransmission = waterColor.rgb * (ambientColor.rgb * 0.85 + diff * lightColor.rgb * 0.55); + vec3 absorbed = mix(litTransmission, waterColor.rgb * 0.52, opticalDepth); + absorbed += vec3(crest); + + // Schlick Fresnel with water-like F0. + const float F0 = 0.02; + float fresnel = F0 + (1.0 - F0) * pow(1.0 - ndotv, 5.0); + vec2 refractOffset = norm.xz * (0.012 + 0.02 * fresnel); + vec2 refractUV = clamp(ScreenUV + refractOffset, vec2(0.001), vec2(0.999)); + vec3 sceneRefract = texture(SceneColor, refractUV).rgb; + + float sceneDepth = texture(SceneDepth, refractUV).r; + float waterDepth = clamp((sceneDepth - gl_FragCoord.z) * 180.0, 0.0, 1.0); + float depthBlend = waterDepth; + // Fallback when sampled depth does not provide meaningful separation. + if (sceneDepth <= gl_FragCoord.z + 1e-4) { + depthBlend = 0.45 + opticalDepth * 0.40; + } + depthBlend = clamp(depthBlend, 0.28, 1.0); + vec3 refractedTint = mix(sceneRefract, absorbed, depthBlend); + + vec3 specular = spec * lightColor.rgb * (0.45 + 0.75 * fresnel) + + sparkle * lightColor.rgb * 0.30; + // Add a clear surface reflection lobe at grazing angles. + vec3 envReflect = mix(fogColor.rgb, lightColor.rgb, 0.38) * vec3(0.75, 0.86, 1.0); + vec3 reflection = envReflect * (0.45 + 0.55 * fresnel) + specular; + float reflectWeight = clamp(fresnel * 1.15, 0.0, 0.92); + vec3 color = mix(refractedTint, reflection, reflectWeight); + + float alpha = mix(waterAlpha * 1.05, min(1.0, waterAlpha * 1.30), fresnel) * alphaScale; + alpha *= smoothstep(1600.0, 350.0, dist); + alpha = clamp(alpha, 0.50, 1.0); float fogFactor = clamp((fogParams.y - dist) / (fogParams.y - fogParams.x), 0.0, 1.0); color = mix(fogColor.rgb, color, fogFactor); diff --git a/assets/shaders/water.vert.glsl b/assets/shaders/water.vert.glsl index f066adef..7547f891 100644 --- a/assets/shaders/water.vert.glsl +++ b/assets/shaders/water.vert.glsl @@ -27,6 +27,7 @@ layout(location = 0) out vec3 FragPos; layout(location = 1) out vec3 Normal; layout(location = 2) out vec2 TexCoord; layout(location = 3) out float WaveOffset; +layout(location = 4) out vec2 ScreenUV; float hashGrid(vec2 p) { return fract(sin(dot(floor(p), vec2(127.1, 311.7))) * 43758.5453); @@ -57,5 +58,8 @@ void main() { FragPos = worldPos.xyz; TexCoord = aTexCoord; - gl_Position = projection * view * worldPos; + vec4 clipPos = projection * view * worldPos; + gl_Position = clipPos; + vec2 ndc = clipPos.xy / max(clipPos.w, 1e-5); + ScreenUV = ndc * 0.5 + 0.5; } diff --git a/include/rendering/vk_context.hpp b/include/rendering/vk_context.hpp index ce6f5b2a..2496c43e 100644 --- a/include/rendering/vk_context.hpp +++ b/include/rendering/vk_context.hpp @@ -59,6 +59,7 @@ public: VkFormat getSwapchainFormat() const { return swapchainFormat; } VkExtent2D getSwapchainExtent() const { return swapchainExtent; } const std::vector& getSwapchainImageViews() const { return swapchainImageViews; } + const std::vector& getSwapchainImages() const { return swapchainImages; } uint32_t getSwapchainImageCount() const { return static_cast(swapchainImages.size()); } uint32_t getCurrentFrame() const { return currentFrame; } @@ -76,6 +77,14 @@ public: VkSampleCountFlagBits getMsaaSamples() const { return msaaSamples_; } void setMsaaSamples(VkSampleCountFlagBits samples); VkSampleCountFlagBits getMaxUsableSampleCount() const; + VkImage getDepthImage() const { return depthImage; } + VkImage getDepthCopySourceImage() const { + return (depthResolveImage != VK_NULL_HANDLE) ? depthResolveImage : depthImage; + } + bool isDepthCopySourceMsaa() const { + return (depthResolveImage == VK_NULL_HANDLE) && (msaaSamples_ > VK_SAMPLE_COUNT_1_BIT); + } + VkFormat getDepthFormat() const { return depthFormat; } // UI texture upload: creates a Vulkan texture from RGBA data and returns // a VkDescriptorSet suitable for use as ImTextureID. @@ -146,6 +155,15 @@ private: bool createMsaaColorImage(); void destroyMsaaColorImage(); + bool createDepthResolveImage(); + void destroyDepthResolveImage(); + + // MSAA depth resolve support (for sampling/copying resolved depth) + bool depthResolveSupported_ = false; + VkResolveModeFlagBits depthResolveMode_ = VK_RESOLVE_MODE_NONE; + VkImage depthResolveImage = VK_NULL_HANDLE; + VkImageView depthResolveImageView = VK_NULL_HANDLE; + VmaAllocation depthResolveAllocation = VK_NULL_HANDLE; // ImGui resources VkRenderPass imguiRenderPass = VK_NULL_HANDLE; diff --git a/include/rendering/water_renderer.hpp b/include/rendering/water_renderer.hpp index cbd3b121..e4f5e68e 100644 --- a/include/rendering/water_renderer.hpp +++ b/include/rendering/water_renderer.hpp @@ -82,6 +82,11 @@ public: void recreatePipelines(); void render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const Camera& camera, float time); + void captureSceneHistory(VkCommandBuffer cmd, + VkImage srcColorImage, + VkImage srcDepthImage, + VkExtent2D srcExtent, + bool srcDepthIsMsaa); void setEnabled(bool enabled) { renderingEnabled = enabled; } bool isEnabled() const { return renderingEnabled; } @@ -100,6 +105,8 @@ private: void updateMaterialUBO(WaterSurface& surface); VkDescriptorSet allocateMaterialSet(); + void createSceneHistoryResources(VkExtent2D extent, VkFormat colorFormat, VkFormat depthFormat); + void destroySceneHistoryResources(); VkContext* vkCtx = nullptr; @@ -108,8 +115,22 @@ private: VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; VkDescriptorSetLayout materialSetLayout = VK_NULL_HANDLE; VkDescriptorPool materialDescPool = VK_NULL_HANDLE; + VkDescriptorSetLayout sceneSetLayout = VK_NULL_HANDLE; + VkDescriptorPool sceneDescPool = VK_NULL_HANDLE; + VkDescriptorSet sceneSet = VK_NULL_HANDLE; static constexpr uint32_t MAX_WATER_SETS = 2048; + VkSampler sceneColorSampler = VK_NULL_HANDLE; + VkSampler sceneDepthSampler = VK_NULL_HANDLE; + VkImage sceneColorImage = VK_NULL_HANDLE; + VmaAllocation sceneColorAlloc = VK_NULL_HANDLE; + VkImageView sceneColorView = VK_NULL_HANDLE; + VkImage sceneDepthImage = VK_NULL_HANDLE; + VmaAllocation sceneDepthAlloc = VK_NULL_HANDLE; + VkImageView sceneDepthView = VK_NULL_HANDLE; + VkExtent2D sceneHistoryExtent = {0, 0}; + bool sceneHistoryReady = false; + std::vector surfaces; bool renderingEnabled = true; }; diff --git a/src/rendering/m2_renderer.cpp b/src/rendering/m2_renderer.cpp index a390e08b..2d8a34f6 100644 --- a/src/rendering/m2_renderer.cpp +++ b/src/rendering/m2_renderer.cpp @@ -2215,9 +2215,19 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const if (!hasDesiredLOD) targetLOD = 0; } - std::string modelKeyLower = model.name; - std::transform(modelKeyLower.begin(), modelKeyLower.end(), modelKeyLower.begin(), + std::string modelKeyLower = model.name; + std::transform(modelKeyLower.begin(), modelKeyLower.end(), modelKeyLower.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); + const bool foliageLikeModel = + (modelKeyLower.find("tree") != std::string::npos) || + (modelKeyLower.find("bush") != std::string::npos) || + (modelKeyLower.find("foliage") != std::string::npos) || + (modelKeyLower.find("grass") != std::string::npos) || + (modelKeyLower.find("plant") != std::string::npos) || + (modelKeyLower.find("shrub") != std::string::npos) || + (modelKeyLower.find("leaf") != std::string::npos) || + (modelKeyLower.find("leaves") != std::string::npos) || + (modelKeyLower.find("vine") != std::string::npos); for (const auto& batch : model.batches) { if (batch.indexCount == 0) continue; if (!model.isGroundDetail && batch.submeshLevel != targetLOD) continue; @@ -2295,22 +2305,40 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const } } + // Foliage/card-like batches render more stably as cutout (depth-write on) + // instead of alpha-blended sorting. + const bool foliageCutout = + foliageLikeModel && + !model.isSpellEffect && + batch.blendMode <= 3; + const bool forceCutout = + !model.isSpellEffect && + (model.isGroundDetail || + foliageCutout || + batch.blendMode == 1 || + (batch.blendMode >= 2 && !batch.hasAlpha) || + batch.colorKeyBlack); + // Select pipeline based on blend mode uint8_t effectiveBlendMode = batch.blendMode; if (model.isSpellEffect && (effectiveBlendMode == 4 || effectiveBlendMode == 5)) { effectiveBlendMode = 3; } - if (model.isGroundDetail) { - // Treat foliage cards as cutout so they depth-sort correctly. + if (forceCutout) { effectiveBlendMode = 1; } VkPipeline desiredPipeline; - switch (effectiveBlendMode) { - case 0: desiredPipeline = opaquePipeline_; break; - case 1: desiredPipeline = alphaTestPipeline_; break; - case 2: desiredPipeline = alphaPipeline_; break; - default: desiredPipeline = additivePipeline_; break; + if (forceCutout) { + // Use opaque pipeline + shader discard for stable foliage cards. + desiredPipeline = opaquePipeline_; + } else { + switch (effectiveBlendMode) { + case 0: desiredPipeline = opaquePipeline_; break; + case 1: desiredPipeline = alphaTestPipeline_; break; + case 2: desiredPipeline = alphaPipeline_; break; + default: desiredPipeline = additivePipeline_; break; + } } if (desiredPipeline != currentPipeline) { vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, desiredPipeline); @@ -2330,10 +2358,12 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const if (batch.colorKeyBlack) { mat->colorKeyThreshold = (effectiveBlendMode == 4 || effectiveBlendMode == 5) ? 0.7f : 0.08f; } - // Ground detail: force cutout shading for stable two-sided foliage. - if (model.isGroundDetail) { - mat->alphaTest = 1; - mat->unlit = 0; + // Cutout path for foliage/cards. + if (forceCutout) { + mat->alphaTest = foliageCutout ? 2 : 1; + if (model.isGroundDetail) { + mat->unlit = 0; + } } } } diff --git a/src/rendering/minimap.cpp b/src/rendering/minimap.cpp index b47694f3..f5aa54f8 100644 --- a/src/rendering/minimap.cpp +++ b/src/rendering/minimap.cpp @@ -512,9 +512,8 @@ void Minimap::render(VkCommandBuffer cmd, const Camera& playerCamera, float arrowRotation = 0.0f; if (!rotateWithCamera) { - // Prefer authoritative player orientation for north-up minimap arrow. - // Canonical yaw already matches minimap rotation convention: - // 0=north, +pi/2=east. + // Prefer authoritative orientation if provided. This value is expected + // to already match minimap shader rotation convention. if (hasPlayerOrientation) { arrowRotation = playerOrientation; } else { diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index 44d1e163..e5b143aa 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -912,6 +912,15 @@ void Renderer::endFrame() { vkCmdEndRenderPass(currentCmd); + if (waterRenderer && currentImageIndex < vkCtx->getSwapchainImages().size()) { + waterRenderer->captureSceneHistory( + currentCmd, + vkCtx->getSwapchainImages()[currentImageIndex], + vkCtx->getDepthCopySourceImage(), + vkCtx->getSwapchainExtent(), + vkCtx->isDepthCopySourceMsaa()); + } + // Submit and present vkCtx->endFrame(currentCmd, currentImageIndex); currentCmd = VK_NULL_HANDLE; @@ -3185,7 +3194,14 @@ void Renderer::renderWorld(game::World* world, game::GameHandler* gameHandler) { minimapCenter = characterPosition; float minimapPlayerOrientation = 0.0f; bool hasMinimapPlayerOrientation = false; - if (gameHandler) { + if (cameraController) { + // Use the same yaw that drives character model rendering so minimap + // orientation cannot drift by a different axis/sign convention. + float facingRad = glm::radians(characterYaw); + glm::vec3 facingFwd(std::cos(facingRad), std::sin(facingRad), 0.0f); + minimapPlayerOrientation = std::atan2(-facingFwd.x, facingFwd.y); + hasMinimapPlayerOrientation = true; + } else if (gameHandler) { minimapPlayerOrientation = gameHandler->getMovementInfo().orientation; hasMinimapPlayerOrientation = true; } diff --git a/src/rendering/vk_context.cpp b/src/rendering/vk_context.cpp index 9ebec4f4..8dcdef1b 100644 --- a/src/rendering/vk_context.cpp +++ b/src/rendering/vk_context.cpp @@ -146,9 +146,39 @@ bool VkContext::selectPhysicalDevice() { VkPhysicalDeviceProperties props; vkGetPhysicalDeviceProperties(physicalDevice, &props); + uint32_t apiVersion = props.apiVersion; + + VkPhysicalDeviceDepthStencilResolveProperties dsResolveProps{}; + dsResolveProps.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_STENCIL_RESOLVE_PROPERTIES; + VkPhysicalDeviceProperties2 props2{}; + props2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + props2.pNext = &dsResolveProps; + vkGetPhysicalDeviceProperties2(physicalDevice, &props2); + + if (apiVersion >= VK_API_VERSION_1_2) { + VkResolveModeFlags modes = dsResolveProps.supportedDepthResolveModes; + if (modes & VK_RESOLVE_MODE_SAMPLE_ZERO_BIT) { + depthResolveMode_ = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT; + depthResolveSupported_ = true; + } else if (modes & VK_RESOLVE_MODE_MIN_BIT) { + depthResolveMode_ = VK_RESOLVE_MODE_MIN_BIT; + depthResolveSupported_ = true; + } else if (modes & VK_RESOLVE_MODE_MAX_BIT) { + depthResolveMode_ = VK_RESOLVE_MODE_MAX_BIT; + depthResolveSupported_ = true; + } else if (modes & VK_RESOLVE_MODE_AVERAGE_BIT) { + depthResolveMode_ = VK_RESOLVE_MODE_AVERAGE_BIT; + depthResolveSupported_ = true; + } + } else { + depthResolveSupported_ = false; + depthResolveMode_ = VK_RESOLVE_MODE_NONE; + } + LOG_INFO("Vulkan device: ", props.deviceName); LOG_INFO("Vulkan API version: ", VK_VERSION_MAJOR(props.apiVersion), ".", VK_VERSION_MINOR(props.apiVersion), ".", VK_VERSION_PATCH(props.apiVersion)); + LOG_INFO("Depth resolve support: ", depthResolveSupported_ ? "YES" : "NO"); return true; } @@ -209,6 +239,7 @@ bool VkContext::createSwapchain(int width, int height) { .set_desired_format({VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}) .set_desired_present_mode(VK_PRESENT_MODE_FIFO_KHR) // VSync .set_desired_extent(static_cast(width), static_cast(height)) + .set_image_usage_flags(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT) .set_desired_min_image_count(2) .set_old_swapchain(swapchain) // For recreation .build(); @@ -334,7 +365,7 @@ bool VkContext::createDepthBuffer() { imgInfo.arrayLayers = 1; imgInfo.samples = msaaSamples_; imgInfo.tiling = VK_IMAGE_TILING_OPTIMAL; - imgInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + imgInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; VmaAllocationCreateInfo allocInfo{}; allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; @@ -416,6 +447,56 @@ void VkContext::destroyMsaaColorImage() { if (msaaColorImage_) { vmaDestroyImage(allocator, msaaColorImage_, msaaColorAllocation_); msaaColorImage_ = VK_NULL_HANDLE; msaaColorAllocation_ = VK_NULL_HANDLE; } } +bool VkContext::createDepthResolveImage() { + if (msaaSamples_ == VK_SAMPLE_COUNT_1_BIT || !depthResolveSupported_) return true; + + VkImageCreateInfo imgInfo{}; + imgInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imgInfo.imageType = VK_IMAGE_TYPE_2D; + imgInfo.format = depthFormat; + imgInfo.extent = {swapchainExtent.width, swapchainExtent.height, 1}; + imgInfo.mipLevels = 1; + imgInfo.arrayLayers = 1; + imgInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imgInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imgInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + + if (vmaCreateImage(allocator, &imgInfo, &allocInfo, &depthResolveImage, &depthResolveAllocation, nullptr) != VK_SUCCESS) { + LOG_ERROR("Failed to create depth resolve image"); + return false; + } + + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = depthResolveImage; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = depthFormat; + viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.layerCount = 1; + if (vkCreateImageView(device, &viewInfo, nullptr, &depthResolveImageView) != VK_SUCCESS) { + LOG_ERROR("Failed to create depth resolve image view"); + return false; + } + + return true; +} + +void VkContext::destroyDepthResolveImage() { + if (depthResolveImageView) { + vkDestroyImageView(device, depthResolveImageView, nullptr); + depthResolveImageView = VK_NULL_HANDLE; + } + if (depthResolveImage) { + vmaDestroyImage(allocator, depthResolveImage, depthResolveAllocation); + depthResolveImage = VK_NULL_HANDLE; + depthResolveAllocation = VK_NULL_HANDLE; + } +} + VkSampleCountFlagBits VkContext::getMaxUsableSampleCount() const { VkPhysicalDeviceProperties props; vkGetPhysicalDeviceProperties(physicalDevice, &props); @@ -441,12 +522,15 @@ bool VkContext::createImGuiResources() { // Create MSAA color image if needed if (!createMsaaColorImage()) return false; + // Create single-sample depth resolve image for MSAA path (if supported) + if (!createDepthResolveImage()) return false; bool useMsaa = (msaaSamples_ > VK_SAMPLE_COUNT_1_BIT); if (useMsaa) { - // MSAA render pass: 3 attachments (MSAA color, depth, resolve/swapchain) - VkAttachmentDescription attachments[3] = {}; + const bool useDepthResolve = (depthResolveImageView != VK_NULL_HANDLE); + // MSAA render pass: 3 or 4 attachments + VkAttachmentDescription attachments[4] = {}; // Attachment 0: MSAA color target attachments[0].format = swapchainFormat; @@ -462,7 +546,7 @@ bool VkContext::createImGuiResources() { attachments[1].format = depthFormat; attachments[1].samples = msaaSamples_; attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE; attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; @@ -478,56 +562,136 @@ bool VkContext::createImGuiResources() { attachments[2].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; attachments[2].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - VkAttachmentReference colorRef{}; - colorRef.attachment = 0; - colorRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - - VkAttachmentReference depthRef{}; - depthRef.attachment = 1; - depthRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - - VkAttachmentReference resolveRef{}; - resolveRef.attachment = 2; - resolveRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - - VkSubpassDescription subpass{}; - subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpass.colorAttachmentCount = 1; - subpass.pColorAttachments = &colorRef; - subpass.pDepthStencilAttachment = &depthRef; - subpass.pResolveAttachments = &resolveRef; - - VkSubpassDependency dependency{}; - dependency.srcSubpass = VK_SUBPASS_EXTERNAL; - dependency.dstSubpass = 0; - dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; - dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; - dependency.srcAccessMask = 0; - dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; - - VkRenderPassCreateInfo rpInfo{}; - rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - rpInfo.attachmentCount = 3; - rpInfo.pAttachments = attachments; - rpInfo.subpassCount = 1; - rpInfo.pSubpasses = &subpass; - rpInfo.dependencyCount = 1; - rpInfo.pDependencies = &dependency; - - if (vkCreateRenderPass(device, &rpInfo, nullptr, &imguiRenderPass) != VK_SUCCESS) { - LOG_ERROR("Failed to create MSAA render pass"); - return false; + if (useDepthResolve) { + attachments[3].format = depthFormat; + attachments[3].samples = VK_SAMPLE_COUNT_1_BIT; + attachments[3].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachments[3].storeOp = VK_ATTACHMENT_STORE_OP_STORE; + attachments[3].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachments[3].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachments[3].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + attachments[3].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; } - // Framebuffers: [msaaColorView, depthView, swapchainView] + if (useDepthResolve) { + VkAttachmentDescription2 attachments2[4]{}; + for (int i = 0; i < 4; ++i) { + attachments2[i].sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2; + attachments2[i].format = attachments[i].format; + attachments2[i].samples = attachments[i].samples; + attachments2[i].loadOp = attachments[i].loadOp; + attachments2[i].storeOp = attachments[i].storeOp; + attachments2[i].stencilLoadOp = attachments[i].stencilLoadOp; + attachments2[i].stencilStoreOp = attachments[i].stencilStoreOp; + attachments2[i].initialLayout = attachments[i].initialLayout; + attachments2[i].finalLayout = attachments[i].finalLayout; + } + + VkAttachmentReference2 colorRef2{}; + colorRef2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2; + colorRef2.attachment = 0; + colorRef2.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + VkAttachmentReference2 depthRef2{}; + depthRef2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2; + depthRef2.attachment = 1; + depthRef2.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + VkAttachmentReference2 resolveRef2{}; + resolveRef2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2; + resolveRef2.attachment = 2; + resolveRef2.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + VkAttachmentReference2 depthResolveRef2{}; + depthResolveRef2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2; + depthResolveRef2.attachment = 3; + depthResolveRef2.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkSubpassDescriptionDepthStencilResolve dsResolve{}; + dsResolve.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_DEPTH_STENCIL_RESOLVE; + dsResolve.depthResolveMode = depthResolveMode_; + dsResolve.stencilResolveMode = VK_RESOLVE_MODE_NONE; + dsResolve.pDepthStencilResolveAttachment = &depthResolveRef2; + + VkSubpassDescription2 subpass2{}; + subpass2.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2; + subpass2.pNext = &dsResolve; + subpass2.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass2.colorAttachmentCount = 1; + subpass2.pColorAttachments = &colorRef2; + subpass2.pDepthStencilAttachment = &depthRef2; + subpass2.pResolveAttachments = &resolveRef2; + + VkSubpassDependency2 dep2{}; + dep2.sType = VK_STRUCTURE_TYPE_SUBPASS_DEPENDENCY_2; + dep2.srcSubpass = VK_SUBPASS_EXTERNAL; + dep2.dstSubpass = 0; + dep2.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dep2.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dep2.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo2 rpInfo2{}; + rpInfo2.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2; + rpInfo2.attachmentCount = 4; + rpInfo2.pAttachments = attachments2; + rpInfo2.subpassCount = 1; + rpInfo2.pSubpasses = &subpass2; + rpInfo2.dependencyCount = 1; + rpInfo2.pDependencies = &dep2; + + if (vkCreateRenderPass2(device, &rpInfo2, nullptr, &imguiRenderPass) != VK_SUCCESS) { + LOG_ERROR("Failed to create MSAA render pass (depth resolve)"); + return false; + } + } else { + VkAttachmentReference colorRef{}; + colorRef.attachment = 0; + colorRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkAttachmentReference depthRef{}; + depthRef.attachment = 1; + depthRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkAttachmentReference resolveRef{}; + resolveRef.attachment = 2; + resolveRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorRef; + subpass.pDepthStencilAttachment = &depthRef; + subpass.pResolveAttachments = &resolveRef; + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.srcAccessMask = 0; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo rpInfo{}; + rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + rpInfo.attachmentCount = 3; + rpInfo.pAttachments = attachments; + rpInfo.subpassCount = 1; + rpInfo.pSubpasses = &subpass; + rpInfo.dependencyCount = 1; + rpInfo.pDependencies = &dependency; + + if (vkCreateRenderPass(device, &rpInfo, nullptr, &imguiRenderPass) != VK_SUCCESS) { + LOG_ERROR("Failed to create MSAA render pass"); + return false; + } + } + + // Framebuffers: [msaaColorView, depthView, swapchainView, depthResolveView?] swapchainFramebuffers.resize(swapchainImageViews.size()); for (size_t i = 0; i < swapchainImageViews.size(); i++) { - VkImageView fbAttachments[3] = {msaaColorView_, depthImageView, swapchainImageViews[i]}; + VkImageView fbAttachments[4] = {msaaColorView_, depthImageView, swapchainImageViews[i], depthResolveImageView}; VkFramebufferCreateInfo fbInfo{}; fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; fbInfo.renderPass = imguiRenderPass; - fbInfo.attachmentCount = 3; + fbInfo.attachmentCount = useDepthResolve ? 4 : 3; fbInfo.pAttachments = fbAttachments; fbInfo.width = swapchainExtent.width; fbInfo.height = swapchainExtent.height; @@ -556,7 +720,7 @@ bool VkContext::createImGuiResources() { attachments[1].format = depthFormat; attachments[1].samples = VK_SAMPLE_COUNT_1_BIT; attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE; attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; @@ -657,6 +821,7 @@ void VkContext::destroyImGuiResources() { imguiDescriptorPool = VK_NULL_HANDLE; } destroyMsaaColorImage(); + destroyDepthResolveImage(); destroyDepthBuffer(); // Framebuffers are destroyed in destroySwapchain() if (imguiRenderPass) { @@ -848,6 +1013,7 @@ bool VkContext::recreateSwapchain(int width, int height) { .set_desired_format({VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}) .set_desired_present_mode(VK_PRESENT_MODE_FIFO_KHR) .set_desired_extent(static_cast(width), static_cast(height)) + .set_image_usage_flags(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT) .set_desired_min_image_count(2) .set_old_swapchain(oldSwapchain) .build(); @@ -869,8 +1035,9 @@ bool VkContext::recreateSwapchain(int width, int height) { swapchainImages = vkbSwap.get_images().value(); swapchainImageViews = vkbSwap.get_image_views().value(); - // Recreate depth buffer + MSAA color image + // Recreate depth buffer + MSAA color image + depth resolve image destroyMsaaColorImage(); + destroyDepthResolveImage(); destroyDepthBuffer(); // Destroy old render pass (needs recreation if MSAA changed) @@ -881,12 +1048,14 @@ bool VkContext::recreateSwapchain(int width, int height) { if (!createDepthBuffer()) return false; if (!createMsaaColorImage()) return false; + if (!createDepthResolveImage()) return false; bool useMsaa = (msaaSamples_ > VK_SAMPLE_COUNT_1_BIT); if (useMsaa) { - // MSAA render pass: 3 attachments - VkAttachmentDescription attachments[3] = {}; + const bool useDepthResolve = (depthResolveImageView != VK_NULL_HANDLE); + // MSAA render pass: 3 or 4 attachments + VkAttachmentDescription attachments[4] = {}; attachments[0].format = swapchainFormat; attachments[0].samples = msaaSamples_; attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; @@ -899,7 +1068,7 @@ bool VkContext::recreateSwapchain(int width, int height) { attachments[1].format = depthFormat; attachments[1].samples = msaaSamples_; attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE; attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; @@ -914,46 +1083,126 @@ bool VkContext::recreateSwapchain(int width, int height) { attachments[2].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; attachments[2].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - VkAttachmentReference colorRef{0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}; - VkAttachmentReference depthRef{1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL}; - VkAttachmentReference resolveRef{2, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}; + if (useDepthResolve) { + attachments[3].format = depthFormat; + attachments[3].samples = VK_SAMPLE_COUNT_1_BIT; + attachments[3].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachments[3].storeOp = VK_ATTACHMENT_STORE_OP_STORE; + attachments[3].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachments[3].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachments[3].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + attachments[3].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + } - VkSubpassDescription subpass{}; - subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpass.colorAttachmentCount = 1; - subpass.pColorAttachments = &colorRef; - subpass.pDepthStencilAttachment = &depthRef; - subpass.pResolveAttachments = &resolveRef; + if (useDepthResolve) { + VkAttachmentDescription2 attachments2[4]{}; + for (int i = 0; i < 4; ++i) { + attachments2[i].sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2; + attachments2[i].format = attachments[i].format; + attachments2[i].samples = attachments[i].samples; + attachments2[i].loadOp = attachments[i].loadOp; + attachments2[i].storeOp = attachments[i].storeOp; + attachments2[i].stencilLoadOp = attachments[i].stencilLoadOp; + attachments2[i].stencilStoreOp = attachments[i].stencilStoreOp; + attachments2[i].initialLayout = attachments[i].initialLayout; + attachments2[i].finalLayout = attachments[i].finalLayout; + } - VkSubpassDependency dependency{}; - dependency.srcSubpass = VK_SUBPASS_EXTERNAL; - dependency.dstSubpass = 0; - dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; - dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; - dependency.srcAccessMask = 0; - dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + VkAttachmentReference2 colorRef2{}; + colorRef2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2; + colorRef2.attachment = 0; + colorRef2.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + VkAttachmentReference2 depthRef2{}; + depthRef2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2; + depthRef2.attachment = 1; + depthRef2.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + VkAttachmentReference2 resolveRef2{}; + resolveRef2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2; + resolveRef2.attachment = 2; + resolveRef2.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + VkAttachmentReference2 depthResolveRef2{}; + depthResolveRef2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2; + depthResolveRef2.attachment = 3; + depthResolveRef2.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - VkRenderPassCreateInfo rpInfo{}; - rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - rpInfo.attachmentCount = 3; - rpInfo.pAttachments = attachments; - rpInfo.subpassCount = 1; - rpInfo.pSubpasses = &subpass; - rpInfo.dependencyCount = 1; - rpInfo.pDependencies = &dependency; + VkSubpassDescriptionDepthStencilResolve dsResolve{}; + dsResolve.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_DEPTH_STENCIL_RESOLVE; + dsResolve.depthResolveMode = depthResolveMode_; + dsResolve.stencilResolveMode = VK_RESOLVE_MODE_NONE; + dsResolve.pDepthStencilResolveAttachment = &depthResolveRef2; - if (vkCreateRenderPass(device, &rpInfo, nullptr, &imguiRenderPass) != VK_SUCCESS) { - LOG_ERROR("Failed to recreate MSAA render pass"); - return false; + VkSubpassDescription2 subpass2{}; + subpass2.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2; + subpass2.pNext = &dsResolve; + subpass2.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass2.colorAttachmentCount = 1; + subpass2.pColorAttachments = &colorRef2; + subpass2.pDepthStencilAttachment = &depthRef2; + subpass2.pResolveAttachments = &resolveRef2; + + VkSubpassDependency2 dep2{}; + dep2.sType = VK_STRUCTURE_TYPE_SUBPASS_DEPENDENCY_2; + dep2.srcSubpass = VK_SUBPASS_EXTERNAL; + dep2.dstSubpass = 0; + dep2.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dep2.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dep2.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo2 rpInfo2{}; + rpInfo2.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2; + rpInfo2.attachmentCount = 4; + rpInfo2.pAttachments = attachments2; + rpInfo2.subpassCount = 1; + rpInfo2.pSubpasses = &subpass2; + rpInfo2.dependencyCount = 1; + rpInfo2.pDependencies = &dep2; + + if (vkCreateRenderPass2(device, &rpInfo2, nullptr, &imguiRenderPass) != VK_SUCCESS) { + LOG_ERROR("Failed to recreate MSAA render pass (depth resolve)"); + return false; + } + } else { + VkAttachmentReference colorRef{0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}; + VkAttachmentReference depthRef{1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL}; + VkAttachmentReference resolveRef{2, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorRef; + subpass.pDepthStencilAttachment = &depthRef; + subpass.pResolveAttachments = &resolveRef; + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.srcAccessMask = 0; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo rpInfo{}; + rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + rpInfo.attachmentCount = 3; + rpInfo.pAttachments = attachments; + rpInfo.subpassCount = 1; + rpInfo.pSubpasses = &subpass; + rpInfo.dependencyCount = 1; + rpInfo.pDependencies = &dependency; + + if (vkCreateRenderPass(device, &rpInfo, nullptr, &imguiRenderPass) != VK_SUCCESS) { + LOG_ERROR("Failed to recreate MSAA render pass"); + return false; + } } swapchainFramebuffers.resize(swapchainImageViews.size()); for (size_t i = 0; i < swapchainImageViews.size(); i++) { - VkImageView fbAttachments[3] = {msaaColorView_, depthImageView, swapchainImageViews[i]}; + VkImageView fbAttachments[4] = {msaaColorView_, depthImageView, swapchainImageViews[i], depthResolveImageView}; VkFramebufferCreateInfo fbInfo{}; fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; fbInfo.renderPass = imguiRenderPass; - fbInfo.attachmentCount = 3; + fbInfo.attachmentCount = useDepthResolve ? 4 : 3; fbInfo.pAttachments = fbAttachments; fbInfo.width = swapchainExtent.width; fbInfo.height = swapchainExtent.height; @@ -978,7 +1227,7 @@ bool VkContext::recreateSwapchain(int width, int height) { attachments[1].format = depthFormat; attachments[1].samples = VK_SAMPLE_COUNT_1_BIT; attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE; attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; diff --git a/src/rendering/water_renderer.cpp b/src/rendering/water_renderer.cpp index 365bd02f..4dc82f87 100644 --- a/src/rendering/water_renderer.cpp +++ b/src/rendering/water_renderer.cpp @@ -13,6 +13,7 @@ #include #include #include +#include namespace wowee { namespace rendering { @@ -78,19 +79,55 @@ bool WaterRenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLay return false; } + // --- Scene history descriptor set layout (set 2) --- + VkDescriptorSetLayoutBinding sceneColorBinding{}; + sceneColorBinding.binding = 0; + sceneColorBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + sceneColorBinding.descriptorCount = 1; + sceneColorBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + + VkDescriptorSetLayoutBinding sceneDepthBinding{}; + sceneDepthBinding.binding = 1; + sceneDepthBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + sceneDepthBinding.descriptorCount = 1; + sceneDepthBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + + sceneSetLayout = createDescriptorSetLayout(device, {sceneColorBinding, sceneDepthBinding}); + if (!sceneSetLayout) { + LOG_ERROR("WaterRenderer: failed to create scene set layout"); + return false; + } + + VkDescriptorPoolSize scenePoolSize{}; + scenePoolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + scenePoolSize.descriptorCount = 2; + VkDescriptorPoolCreateInfo scenePoolInfo{}; + scenePoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + scenePoolInfo.maxSets = 1; + scenePoolInfo.poolSizeCount = 1; + scenePoolInfo.pPoolSizes = &scenePoolSize; + if (vkCreateDescriptorPool(device, &scenePoolInfo, nullptr, &sceneDescPool) != VK_SUCCESS) { + LOG_ERROR("WaterRenderer: failed to create scene descriptor pool"); + return false; + } + // --- Pipeline layout --- VkPushConstantRange pushRange{}; pushRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; pushRange.offset = 0; pushRange.size = sizeof(WaterPushConstants); - std::vector setLayouts = { perFrameLayout, materialSetLayout }; + std::vector setLayouts = { perFrameLayout, materialSetLayout, sceneSetLayout }; pipelineLayout = createPipelineLayout(device, setLayouts, { pushRange }); if (!pipelineLayout) { LOG_ERROR("WaterRenderer: failed to create pipeline layout"); return false; } + createSceneHistoryResources(vkCtx->getSwapchainExtent(), + vkCtx->getSwapchainFormat(), + vkCtx->getDepthFormat()); + // --- Shaders --- VkShaderModule vertShader, fragShader; if (!vertShader.loadFromFile(device, "assets/shaders/water.vert.spv")) { @@ -147,6 +184,10 @@ void WaterRenderer::recreatePipelines() { if (!vkCtx) return; VkDevice device = vkCtx->getDevice(); + createSceneHistoryResources(vkCtx->getSwapchainExtent(), + vkCtx->getSwapchainFormat(), + vkCtx->getDepthFormat()); + // Destroy old pipeline (keep layout) if (waterPipeline) { vkDestroyPipeline(device, waterPipeline, nullptr); waterPipeline = VK_NULL_HANDLE; } @@ -204,8 +245,11 @@ void WaterRenderer::shutdown() { VkDevice device = vkCtx->getDevice(); vkDeviceWaitIdle(device); + destroySceneHistoryResources(); if (waterPipeline) { vkDestroyPipeline(device, waterPipeline, nullptr); waterPipeline = VK_NULL_HANDLE; } if (pipelineLayout) { vkDestroyPipelineLayout(device, pipelineLayout, nullptr); pipelineLayout = VK_NULL_HANDLE; } + if (sceneDescPool) { vkDestroyDescriptorPool(device, sceneDescPool, nullptr); sceneDescPool = VK_NULL_HANDLE; } + if (sceneSetLayout) { vkDestroyDescriptorSetLayout(device, sceneSetLayout, nullptr); sceneSetLayout = VK_NULL_HANDLE; } if (materialDescPool) { vkDestroyDescriptorPool(device, materialDescPool, nullptr); materialDescPool = VK_NULL_HANDLE; } if (materialSetLayout) { vkDestroyDescriptorSetLayout(device, materialSetLayout, nullptr); materialSetLayout = VK_NULL_HANDLE; } @@ -226,6 +270,150 @@ VkDescriptorSet WaterRenderer::allocateMaterialSet() { return set; } +void WaterRenderer::destroySceneHistoryResources() { + if (!vkCtx) return; + VkDevice device = vkCtx->getDevice(); + if (sceneColorView) { vkDestroyImageView(device, sceneColorView, nullptr); sceneColorView = VK_NULL_HANDLE; } + if (sceneDepthView) { vkDestroyImageView(device, sceneDepthView, nullptr); sceneDepthView = VK_NULL_HANDLE; } + if (sceneColorImage) { vmaDestroyImage(vkCtx->getAllocator(), sceneColorImage, sceneColorAlloc); sceneColorImage = VK_NULL_HANDLE; sceneColorAlloc = VK_NULL_HANDLE; } + if (sceneDepthImage) { vmaDestroyImage(vkCtx->getAllocator(), sceneDepthImage, sceneDepthAlloc); sceneDepthImage = VK_NULL_HANDLE; sceneDepthAlloc = VK_NULL_HANDLE; } + if (sceneColorSampler) { vkDestroySampler(device, sceneColorSampler, nullptr); sceneColorSampler = VK_NULL_HANDLE; } + if (sceneDepthSampler) { vkDestroySampler(device, sceneDepthSampler, nullptr); sceneDepthSampler = VK_NULL_HANDLE; } + sceneSet = VK_NULL_HANDLE; + sceneHistoryExtent = {0, 0}; + sceneHistoryReady = false; +} + +void WaterRenderer::createSceneHistoryResources(VkExtent2D extent, VkFormat colorFormat, VkFormat depthFormat) { + if (!vkCtx || extent.width == 0 || extent.height == 0 || !sceneSetLayout || !sceneDescPool) return; + VkDevice device = vkCtx->getDevice(); + + destroySceneHistoryResources(); + vkResetDescriptorPool(device, sceneDescPool, 0); + sceneHistoryExtent = extent; + + VkImageCreateInfo colorImgInfo{}; + colorImgInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + colorImgInfo.imageType = VK_IMAGE_TYPE_2D; + colorImgInfo.format = colorFormat; + colorImgInfo.extent = {extent.width, extent.height, 1}; + colorImgInfo.mipLevels = 1; + colorImgInfo.arrayLayers = 1; + colorImgInfo.samples = VK_SAMPLE_COUNT_1_BIT; + colorImgInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + colorImgInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + colorImgInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + VmaAllocationCreateInfo allocCI{}; + allocCI.usage = VMA_MEMORY_USAGE_GPU_ONLY; + if (vmaCreateImage(vkCtx->getAllocator(), &colorImgInfo, &allocCI, &sceneColorImage, &sceneColorAlloc, nullptr) != VK_SUCCESS) { + LOG_ERROR("WaterRenderer: failed to create scene color history image"); + return; + } + + VkImageCreateInfo depthImgInfo = colorImgInfo; + depthImgInfo.format = depthFormat; + if (vmaCreateImage(vkCtx->getAllocator(), &depthImgInfo, &allocCI, &sceneDepthImage, &sceneDepthAlloc, nullptr) != VK_SUCCESS) { + LOG_ERROR("WaterRenderer: failed to create scene depth history image"); + return; + } + + VkImageViewCreateInfo colorViewInfo{}; + colorViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + colorViewInfo.image = sceneColorImage; + colorViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + colorViewInfo.format = colorFormat; + colorViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + colorViewInfo.subresourceRange.levelCount = 1; + colorViewInfo.subresourceRange.layerCount = 1; + if (vkCreateImageView(device, &colorViewInfo, nullptr, &sceneColorView) != VK_SUCCESS) { + LOG_ERROR("WaterRenderer: failed to create scene color history view"); + return; + } + + VkImageViewCreateInfo depthViewInfo = colorViewInfo; + depthViewInfo.image = sceneDepthImage; + depthViewInfo.format = depthFormat; + depthViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + if (vkCreateImageView(device, &depthViewInfo, nullptr, &sceneDepthView) != VK_SUCCESS) { + LOG_ERROR("WaterRenderer: failed to create scene depth history view"); + return; + } + + VkSamplerCreateInfo sampCI{}; + sampCI.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + sampCI.magFilter = VK_FILTER_LINEAR; + sampCI.minFilter = VK_FILTER_LINEAR; + sampCI.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + sampCI.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + sampCI.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + if (vkCreateSampler(device, &sampCI, nullptr, &sceneColorSampler) != VK_SUCCESS) { + LOG_ERROR("WaterRenderer: failed to create scene color sampler"); + return; + } + sampCI.magFilter = VK_FILTER_NEAREST; + sampCI.minFilter = VK_FILTER_NEAREST; + if (vkCreateSampler(device, &sampCI, nullptr, &sceneDepthSampler) != VK_SUCCESS) { + LOG_ERROR("WaterRenderer: failed to create scene depth sampler"); + return; + } + + VkDescriptorSetAllocateInfo ai{}; + ai.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + ai.descriptorPool = sceneDescPool; + ai.descriptorSetCount = 1; + ai.pSetLayouts = &sceneSetLayout; + if (vkAllocateDescriptorSets(device, &ai, &sceneSet) != VK_SUCCESS) { + LOG_ERROR("WaterRenderer: failed to allocate scene descriptor set"); + sceneSet = VK_NULL_HANDLE; + return; + } + + VkDescriptorImageInfo colorInfo{}; + colorInfo.sampler = sceneColorSampler; + colorInfo.imageView = sceneColorView; + colorInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + VkDescriptorImageInfo depthInfo{}; + depthInfo.sampler = sceneDepthSampler; + depthInfo.imageView = sceneDepthView; + depthInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + std::array writes{}; + writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + writes[0].dstSet = sceneSet; + writes[0].dstBinding = 0; + writes[0].descriptorCount = 1; + writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + writes[0].pImageInfo = &colorInfo; + writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + writes[1].dstSet = sceneSet; + writes[1].dstBinding = 1; + writes[1].descriptorCount = 1; + writes[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + writes[1].pImageInfo = &depthInfo; + vkUpdateDescriptorSets(device, static_cast(writes.size()), writes.data(), 0, nullptr); + + // Initialize history images to shader-read layout so first frame samples are defined. + vkCtx->immediateSubmit([&](VkCommandBuffer cmd) { + VkImageMemoryBarrier barriers[2]{}; + barriers[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barriers[0].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + barriers[0].newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barriers[0].image = sceneColorImage; + barriers[0].subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + barriers[0].dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + barriers[1] = barriers[0]; + barriers[1].image = sceneDepthImage; + barriers[1].subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + + vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + 0, 0, nullptr, 0, nullptr, 2, barriers); + }); +} + void WaterRenderer::updateMaterialUBO(WaterSurface& surface) { glm::vec4 color = getLiquidColor(surface.liquidType); float alpha = getLiquidAlpha(surface.liquidType); @@ -497,11 +685,14 @@ void WaterRenderer::clear() { void WaterRenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const Camera& /*camera*/, float /*time*/) { if (!renderingEnabled || surfaces.empty() || !waterPipeline) return; + if (!sceneSet) return; vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, waterPipeline); vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &perFrameSet, 0, nullptr); + vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, + 2, 1, &sceneSet, 0, nullptr); for (const auto& surface : surfaces) { if (surface.vertexBuffer == VK_NULL_HANDLE || surface.indexCount == 0) continue; @@ -532,6 +723,103 @@ void WaterRenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, } } +void WaterRenderer::captureSceneHistory(VkCommandBuffer cmd, + VkImage srcColorImage, + VkImage srcDepthImage, + VkExtent2D srcExtent, + bool srcDepthIsMsaa) { + if (!vkCtx || !cmd || !sceneColorImage || !sceneDepthImage || srcExtent.width == 0 || srcExtent.height == 0) { + return; + } + + VkExtent2D copyExtent{ + std::min(srcExtent.width, sceneHistoryExtent.width), + std::min(srcExtent.height, sceneHistoryExtent.height) + }; + if (copyExtent.width == 0 || copyExtent.height == 0) return; + + auto barrier2 = [&](VkImage image, + VkImageAspectFlags aspect, + VkImageLayout oldLayout, + VkImageLayout newLayout, + VkAccessFlags srcAccess, + VkAccessFlags dstAccess, + VkPipelineStageFlags srcStage, + VkPipelineStageFlags dstStage) { + VkImageMemoryBarrier b{}; + b.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + b.oldLayout = oldLayout; + b.newLayout = newLayout; + b.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + b.image = image; + b.subresourceRange.aspectMask = aspect; + b.subresourceRange.baseMipLevel = 0; + b.subresourceRange.levelCount = 1; + b.subresourceRange.baseArrayLayer = 0; + b.subresourceRange.layerCount = 1; + b.srcAccessMask = srcAccess; + b.dstAccessMask = dstAccess; + vkCmdPipelineBarrier(cmd, srcStage, dstStage, 0, 0, nullptr, 0, nullptr, 1, &b); + }; + + // Color source: final render pass layout is PRESENT_SRC. + barrier2(srcColorImage, VK_IMAGE_ASPECT_COLOR_BIT, + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + 0, VK_ACCESS_TRANSFER_READ_BIT, + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); + barrier2(sceneColorImage, VK_IMAGE_ASPECT_COLOR_BIT, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); + + VkImageCopy colorCopy{}; + colorCopy.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; + colorCopy.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; + colorCopy.extent = {copyExtent.width, copyExtent.height, 1}; + vkCmdCopyImage(cmd, srcColorImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + sceneColorImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &colorCopy); + + barrier2(sceneColorImage, VK_IMAGE_ASPECT_COLOR_BIT, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); + barrier2(srcColorImage, VK_IMAGE_ASPECT_COLOR_BIT, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + VK_ACCESS_TRANSFER_READ_BIT, 0, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT); + + // Depth source: only copy when source is single-sampled. + if (!srcDepthIsMsaa && srcDepthImage != VK_NULL_HANDLE) { + barrier2(srcDepthImage, VK_IMAGE_ASPECT_DEPTH_BIT, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, + VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); + barrier2(sceneDepthImage, VK_IMAGE_ASPECT_DEPTH_BIT, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); + + VkImageCopy depthCopy{}; + depthCopy.srcSubresource = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 0, 1}; + depthCopy.dstSubresource = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 0, 1}; + depthCopy.extent = {copyExtent.width, copyExtent.height, 1}; + vkCmdCopyImage(cmd, srcDepthImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + sceneDepthImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &depthCopy); + + barrier2(sceneDepthImage, VK_IMAGE_ASPECT_DEPTH_BIT, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); + barrier2(srcDepthImage, VK_IMAGE_ASPECT_DEPTH_BIT, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT); + } + + sceneHistoryReady = true; +} + // ============================================================== // Mesh creation (Vulkan upload instead of GL) // ==============================================================