Stabilize Vulkan rendering state for minimap, foliage, and water

This commit is contained in:
Kelsi 2026-02-22 09:34:27 -08:00
parent 8efc1548dc
commit bd0305f6dd
10 changed files with 834 additions and 117 deletions

View file

@ -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);
}

View file

@ -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);

View file

@ -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;
}

View file

@ -59,6 +59,7 @@ public:
VkFormat getSwapchainFormat() const { return swapchainFormat; }
VkExtent2D getSwapchainExtent() const { return swapchainExtent; }
const std::vector<VkImageView>& getSwapchainImageViews() const { return swapchainImageViews; }
const std::vector<VkImage>& getSwapchainImages() const { return swapchainImages; }
uint32_t getSwapchainImageCount() const { return static_cast<uint32_t>(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;

View file

@ -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<WaterSurface> surfaces;
bool renderingEnabled = true;
};

View file

@ -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<char>(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;
}
}
}
}

View file

@ -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 {

View file

@ -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;
}

View file

@ -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<uint32_t>(width), static_cast<uint32_t>(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<uint32_t>(width), static_cast<uint32_t>(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;

View file

@ -13,6 +13,7 @@
#include <cmath>
#include <cstring>
#include <limits>
#include <array>
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<VkDescriptorSetLayout> setLayouts = { perFrameLayout, materialSetLayout };
std::vector<VkDescriptorSetLayout> 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<VkWriteDescriptorSet, 2> 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<uint32_t>(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)
// ==============================================================