mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Stabilize Vulkan rendering state for minimap, foliage, and water
This commit is contained in:
parent
8efc1548dc
commit
bd0305f6dd
10 changed files with 834 additions and 117 deletions
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
// ==============================================================
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue