mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-03 00:03:50 +00:00
Add water refraction toggle with per-frame scene history
Fix VK_ERROR_DEVICE_LOST crash by allocating per-frame scene history images (color + depth) instead of a single shared image that raced between frames in flight. Water refraction can now be toggled via Settings > Video > Water Refraction. Without refraction: richer blue base colors, animated caustic shimmer, and normal-based color shifts give the water visible life. With refraction: clean screen-space refraction with Beer-Lambert absorption. Disabling clears scene history to black for immediate fallback.
This commit is contained in:
parent
7630c7aec7
commit
5a227c0376
8 changed files with 323 additions and 191 deletions
|
|
@ -226,11 +226,32 @@ void main() {
|
||||||
float depthFade = 1.0 - exp(-verticalDepth * 0.15);
|
float depthFade = 1.0 - exp(-verticalDepth * 0.15);
|
||||||
vec3 waterBody = mix(shallowColor, deepColor, depthFade);
|
vec3 waterBody = mix(shallowColor, deepColor, depthFade);
|
||||||
|
|
||||||
vec3 refractedColor = mix(foggedScene * absorbed, waterBody, depthFade * 0.7);
|
// Detect if scene history is available (scene data captured for refraction)
|
||||||
|
float sceneBrightness = dot(sceneRefract, vec3(0.299, 0.587, 0.114));
|
||||||
|
bool hasSceneData = (sceneBrightness > 0.003);
|
||||||
|
|
||||||
if (verticalDepth < 0.01) {
|
// Animated caustic shimmer — only without refraction (refraction already provides movement)
|
||||||
float opticalDepth = 1.0 - exp(-dist * 0.004);
|
if (!hasSceneData) {
|
||||||
refractedColor = mix(foggedScene, waterBody, opticalDepth * 0.6);
|
float caustic1 = noiseValue(FragPos.xy * 1.8 + time * vec2(0.3, 0.15));
|
||||||
|
float caustic2 = noiseValue(FragPos.xy * 3.2 - time * vec2(0.2, 0.35));
|
||||||
|
float causticPattern = caustic1 * 0.6 + caustic2 * 0.4;
|
||||||
|
vec3 causticTint = vec3(0.08, 0.18, 0.28) * smoothstep(0.35, 0.75, causticPattern);
|
||||||
|
waterBody += causticTint;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 refractedColor;
|
||||||
|
if (hasSceneData) {
|
||||||
|
refractedColor = mix(foggedScene * absorbed, waterBody, depthFade * 0.7);
|
||||||
|
if (verticalDepth < 0.01) {
|
||||||
|
float opticalDepth = 1.0 - exp(-dist * 0.004);
|
||||||
|
refractedColor = mix(foggedScene, waterBody, opticalDepth * 0.6);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No refraction data — use lit water body with animated variation
|
||||||
|
vec3 litWater = waterBody * (ambientColor.rgb * 0.8 + NdotL * lightColor.rgb * 0.6);
|
||||||
|
float normalShift = dot(detailNorm.xy, vec2(0.5, 0.5));
|
||||||
|
litWater += vec3(0.02, 0.06, 0.10) * normalShift;
|
||||||
|
refractedColor = litWater;
|
||||||
}
|
}
|
||||||
|
|
||||||
vec3 litBase = waterBody * (ambientColor.rgb * 0.7 + NdotL * lightColor.rgb * 0.5);
|
vec3 litBase = waterBody * (ambientColor.rgb * 0.7 + NdotL * lightColor.rgb * 0.5);
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -256,6 +256,9 @@ public:
|
||||||
bool areShadowsEnabled() const { return shadowsEnabled; }
|
bool areShadowsEnabled() const { return shadowsEnabled; }
|
||||||
void setMsaaSamples(VkSampleCountFlagBits samples);
|
void setMsaaSamples(VkSampleCountFlagBits samples);
|
||||||
|
|
||||||
|
void setWaterRefractionEnabled(bool enabled);
|
||||||
|
bool isWaterRefractionEnabled() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void applyMsaaChange();
|
void applyMsaaChange();
|
||||||
VkSampleCountFlagBits pendingMsaaSamples_ = VK_SAMPLE_COUNT_1_BIT;
|
VkSampleCountFlagBits pendingMsaaSamples_ = VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
|
|
||||||
|
|
@ -93,12 +93,13 @@ public:
|
||||||
bool hasWater1xPass() const { return water1xRenderPass != VK_NULL_HANDLE; }
|
bool hasWater1xPass() const { return water1xRenderPass != VK_NULL_HANDLE; }
|
||||||
VkRenderPass getWater1xRenderPass() const { return water1xRenderPass; }
|
VkRenderPass getWater1xRenderPass() const { return water1xRenderPass; }
|
||||||
|
|
||||||
void render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const Camera& camera, float time, bool use1x = false);
|
void render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const Camera& camera, float time, bool use1x = false, uint32_t frameIndex = 0);
|
||||||
void captureSceneHistory(VkCommandBuffer cmd,
|
void captureSceneHistory(VkCommandBuffer cmd,
|
||||||
VkImage srcColorImage,
|
VkImage srcColorImage,
|
||||||
VkImage srcDepthImage,
|
VkImage srcDepthImage,
|
||||||
VkExtent2D srcExtent,
|
VkExtent2D srcExtent,
|
||||||
bool srcDepthIsMsaa);
|
bool srcDepthIsMsaa,
|
||||||
|
uint32_t frameIndex = 0);
|
||||||
|
|
||||||
// --- Planar reflection pass ---
|
// --- Planar reflection pass ---
|
||||||
// Call sequence: beginReflectionPass → [render scene] → endReflectionPass
|
// Call sequence: beginReflectionPass → [render scene] → endReflectionPass
|
||||||
|
|
@ -124,6 +125,9 @@ public:
|
||||||
void setEnabled(bool enabled) { renderingEnabled = enabled; }
|
void setEnabled(bool enabled) { renderingEnabled = enabled; }
|
||||||
bool isEnabled() const { return renderingEnabled; }
|
bool isEnabled() const { return renderingEnabled; }
|
||||||
|
|
||||||
|
void setRefractionEnabled(bool enabled);
|
||||||
|
bool isRefractionEnabled() const { return refractionEnabled; }
|
||||||
|
|
||||||
std::optional<float> getWaterHeightAt(float glX, float glY) const;
|
std::optional<float> getWaterHeightAt(float glX, float glY) const;
|
||||||
/// Like getWaterHeightAt but only returns water surfaces whose height is
|
/// Like getWaterHeightAt but only returns water surfaces whose height is
|
||||||
/// close to the query Z (within maxAbove units above). Avoids false
|
/// close to the query Z (within maxAbove units above). Avoids false
|
||||||
|
|
@ -159,17 +163,22 @@ private:
|
||||||
VkDescriptorPool materialDescPool = VK_NULL_HANDLE;
|
VkDescriptorPool materialDescPool = VK_NULL_HANDLE;
|
||||||
VkDescriptorSetLayout sceneSetLayout = VK_NULL_HANDLE;
|
VkDescriptorSetLayout sceneSetLayout = VK_NULL_HANDLE;
|
||||||
VkDescriptorPool sceneDescPool = VK_NULL_HANDLE;
|
VkDescriptorPool sceneDescPool = VK_NULL_HANDLE;
|
||||||
VkDescriptorSet sceneSet = VK_NULL_HANDLE;
|
|
||||||
static constexpr uint32_t MAX_WATER_SETS = 16384;
|
static constexpr uint32_t MAX_WATER_SETS = 16384;
|
||||||
|
|
||||||
VkSampler sceneColorSampler = VK_NULL_HANDLE;
|
VkSampler sceneColorSampler = VK_NULL_HANDLE;
|
||||||
VkSampler sceneDepthSampler = VK_NULL_HANDLE;
|
VkSampler sceneDepthSampler = VK_NULL_HANDLE;
|
||||||
VkImage sceneColorImage = VK_NULL_HANDLE;
|
// Per-frame scene history to avoid race between frames in flight
|
||||||
VmaAllocation sceneColorAlloc = VK_NULL_HANDLE;
|
static constexpr uint32_t SCENE_HISTORY_FRAMES = 2;
|
||||||
VkImageView sceneColorView = VK_NULL_HANDLE;
|
struct PerFrameSceneHistory {
|
||||||
VkImage sceneDepthImage = VK_NULL_HANDLE;
|
VkImage colorImage = VK_NULL_HANDLE;
|
||||||
VmaAllocation sceneDepthAlloc = VK_NULL_HANDLE;
|
VmaAllocation colorAlloc = VK_NULL_HANDLE;
|
||||||
VkImageView sceneDepthView = VK_NULL_HANDLE;
|
VkImageView colorView = VK_NULL_HANDLE;
|
||||||
|
VkImage depthImage = VK_NULL_HANDLE;
|
||||||
|
VmaAllocation depthAlloc = VK_NULL_HANDLE;
|
||||||
|
VkImageView depthView = VK_NULL_HANDLE;
|
||||||
|
VkDescriptorSet sceneSet = VK_NULL_HANDLE;
|
||||||
|
};
|
||||||
|
PerFrameSceneHistory sceneHistory[SCENE_HISTORY_FRAMES];
|
||||||
VkExtent2D sceneHistoryExtent = {0, 0};
|
VkExtent2D sceneHistoryExtent = {0, 0};
|
||||||
bool sceneHistoryReady = false;
|
bool sceneHistoryReady = false;
|
||||||
mutable uint32_t renderDiagCounter_ = 0;
|
mutable uint32_t renderDiagCounter_ = 0;
|
||||||
|
|
@ -200,6 +209,7 @@ private:
|
||||||
|
|
||||||
std::vector<WaterSurface> surfaces;
|
std::vector<WaterSurface> surfaces;
|
||||||
bool renderingEnabled = true;
|
bool renderingEnabled = true;
|
||||||
|
bool refractionEnabled = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace rendering
|
} // namespace rendering
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,7 @@ private:
|
||||||
bool pendingVsync = false;
|
bool pendingVsync = false;
|
||||||
int pendingResIndex = 0;
|
int pendingResIndex = 0;
|
||||||
bool pendingShadows = true;
|
bool pendingShadows = true;
|
||||||
|
bool pendingWaterRefraction = false;
|
||||||
int pendingMasterVolume = 100;
|
int pendingMasterVolume = 100;
|
||||||
int pendingMusicVolume = 30;
|
int pendingMusicVolume = 30;
|
||||||
int pendingAmbientVolume = 100;
|
int pendingAmbientVolume = 100;
|
||||||
|
|
@ -123,6 +124,7 @@ private:
|
||||||
bool minimapSettingsApplied_ = false;
|
bool minimapSettingsApplied_ = false;
|
||||||
bool volumeSettingsApplied_ = false; // True once saved volume settings applied to audio managers
|
bool volumeSettingsApplied_ = false; // True once saved volume settings applied to audio managers
|
||||||
bool msaaSettingsApplied_ = false; // True once saved MSAA setting applied to renderer
|
bool msaaSettingsApplied_ = false; // True once saved MSAA setting applied to renderer
|
||||||
|
bool waterRefractionApplied_ = false;
|
||||||
bool normalMapSettingsApplied_ = false; // True once saved normal map/POM settings applied
|
bool normalMapSettingsApplied_ = false; // True once saved normal map/POM settings applied
|
||||||
|
|
||||||
// Mute state: mute bypasses master volume without touching slider values
|
// Mute state: mute bypasses master volume without touching slider values
|
||||||
|
|
|
||||||
|
|
@ -855,6 +855,14 @@ void Renderer::unregisterPreview(CharacterPreview* preview) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Renderer::setWaterRefractionEnabled(bool enabled) {
|
||||||
|
if (waterRenderer) waterRenderer->setRefractionEnabled(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Renderer::isWaterRefractionEnabled() const {
|
||||||
|
return waterRenderer && waterRenderer->isRefractionEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
void Renderer::setMsaaSamples(VkSampleCountFlagBits samples) {
|
void Renderer::setMsaaSamples(VkSampleCountFlagBits samples) {
|
||||||
if (!vkCtx) return;
|
if (!vkCtx) return;
|
||||||
|
|
||||||
|
|
@ -1054,20 +1062,27 @@ void Renderer::endFrame() {
|
||||||
|
|
||||||
vkCmdEndRenderPass(currentCmd);
|
vkCmdEndRenderPass(currentCmd);
|
||||||
|
|
||||||
// Scene-history capture is disabled: with MAX_FRAMES_IN_FLIGHT=2, the single
|
uint32_t frame = vkCtx->getCurrentFrame();
|
||||||
// sceneColorImage can race between frame N-1's water shader read and frame N's
|
|
||||||
// transfer write, eventually causing VK_ERROR_DEVICE_LOST. Water renders
|
|
||||||
// without refraction until per-frame scene-history images are implemented.
|
|
||||||
// TODO: allocate per-frame sceneColor/Depth images to fix the race.
|
|
||||||
|
|
||||||
// Render water in separate 1x pass (without scene refraction for now)
|
// Capture scene color/depth into per-frame history images for water refraction
|
||||||
|
if (waterRenderer && waterRenderer->isRefractionEnabled() && waterRenderer->hasSurfaces()
|
||||||
|
&& currentImageIndex < vkCtx->getSwapchainImages().size()) {
|
||||||
|
waterRenderer->captureSceneHistory(
|
||||||
|
currentCmd,
|
||||||
|
vkCtx->getSwapchainImages()[currentImageIndex],
|
||||||
|
vkCtx->getDepthCopySourceImage(),
|
||||||
|
vkCtx->getSwapchainExtent(),
|
||||||
|
vkCtx->isDepthCopySourceMsaa(),
|
||||||
|
frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render water in separate 1x pass after MSAA resolve + scene capture
|
||||||
bool waterDeferred = waterRenderer && waterRenderer->hasSurfaces() && waterRenderer->hasWater1xPass()
|
bool waterDeferred = waterRenderer && waterRenderer->hasSurfaces() && waterRenderer->hasWater1xPass()
|
||||||
&& vkCtx->getMsaaSamples() != VK_SAMPLE_COUNT_1_BIT;
|
&& vkCtx->getMsaaSamples() != VK_SAMPLE_COUNT_1_BIT;
|
||||||
if (waterDeferred && camera) {
|
if (waterDeferred && camera) {
|
||||||
VkExtent2D ext = vkCtx->getSwapchainExtent();
|
VkExtent2D ext = vkCtx->getSwapchainExtent();
|
||||||
uint32_t frame = vkCtx->getCurrentFrame();
|
|
||||||
if (waterRenderer->beginWater1xPass(currentCmd, currentImageIndex, ext)) {
|
if (waterRenderer->beginWater1xPass(currentCmd, currentImageIndex, ext)) {
|
||||||
waterRenderer->render(currentCmd, perFrameDescSets[frame], *camera, globalTime, true);
|
waterRenderer->render(currentCmd, perFrameDescSets[frame], *camera, globalTime, true, frame);
|
||||||
waterRenderer->endWater1xPass(currentCmd);
|
waterRenderer->endWater1xPass(currentCmd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3268,7 +3283,7 @@ void Renderer::renderWorld(game::World* world, game::GameHandler* gameHandler) {
|
||||||
bool waterDeferred = waterRenderer && waterRenderer->hasWater1xPass()
|
bool waterDeferred = waterRenderer && waterRenderer->hasWater1xPass()
|
||||||
&& vkCtx->getMsaaSamples() != VK_SAMPLE_COUNT_1_BIT;
|
&& vkCtx->getMsaaSamples() != VK_SAMPLE_COUNT_1_BIT;
|
||||||
if (waterRenderer && camera && !waterDeferred) {
|
if (waterRenderer && camera && !waterDeferred) {
|
||||||
waterRenderer->render(currentCmd, perFrameSet, *camera, globalTime);
|
waterRenderer->render(currentCmd, perFrameSet, *camera, globalTime, false, vkCtx->getCurrentFrame());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Weather particles
|
// Weather particles
|
||||||
|
|
|
||||||
|
|
@ -118,15 +118,15 @@ bool WaterRenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLay
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pool needs 3 combined image samplers + 1 uniform buffer
|
// Pool needs 3 combined image samplers + 1 uniform buffer per frame
|
||||||
std::array<VkDescriptorPoolSize, 2> scenePoolSizes{};
|
std::array<VkDescriptorPoolSize, 2> scenePoolSizes{};
|
||||||
scenePoolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
scenePoolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||||
scenePoolSizes[0].descriptorCount = 3;
|
scenePoolSizes[0].descriptorCount = 3 * SCENE_HISTORY_FRAMES;
|
||||||
scenePoolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
scenePoolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||||
scenePoolSizes[1].descriptorCount = 1;
|
scenePoolSizes[1].descriptorCount = SCENE_HISTORY_FRAMES;
|
||||||
VkDescriptorPoolCreateInfo scenePoolInfo{};
|
VkDescriptorPoolCreateInfo scenePoolInfo{};
|
||||||
scenePoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
scenePoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
||||||
scenePoolInfo.maxSets = 1;
|
scenePoolInfo.maxSets = SCENE_HISTORY_FRAMES;
|
||||||
scenePoolInfo.poolSizeCount = static_cast<uint32_t>(scenePoolSizes.size());
|
scenePoolInfo.poolSizeCount = static_cast<uint32_t>(scenePoolSizes.size());
|
||||||
scenePoolInfo.pPoolSizes = scenePoolSizes.data();
|
scenePoolInfo.pPoolSizes = scenePoolSizes.data();
|
||||||
if (vkCreateDescriptorPool(device, &scenePoolInfo, nullptr, &sceneDescPool) != VK_SUCCESS) {
|
if (vkCreateDescriptorPool(device, &scenePoolInfo, nullptr, &sceneDescPool) != VK_SUCCESS) {
|
||||||
|
|
@ -267,6 +267,47 @@ void WaterRenderer::recreatePipelines() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WaterRenderer::setRefractionEnabled(bool enabled) {
|
||||||
|
if (refractionEnabled == enabled) return;
|
||||||
|
refractionEnabled = enabled;
|
||||||
|
|
||||||
|
// When turning off, clear scene history images to black so the shader
|
||||||
|
// detects "no data" and uses the non-refraction path.
|
||||||
|
if (!enabled && vkCtx) {
|
||||||
|
vkCtx->immediateSubmit([&](VkCommandBuffer cmd) {
|
||||||
|
for (uint32_t f = 0; f < SCENE_HISTORY_FRAMES; f++) {
|
||||||
|
auto& sh = sceneHistory[f];
|
||||||
|
if (!sh.colorImage) continue;
|
||||||
|
|
||||||
|
VkImageMemoryBarrier toTransfer{};
|
||||||
|
toTransfer.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||||
|
toTransfer.oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||||
|
toTransfer.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||||
|
toTransfer.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||||
|
toTransfer.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||||
|
toTransfer.image = sh.colorImage;
|
||||||
|
toTransfer.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||||
|
toTransfer.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||||
|
toTransfer.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||||
|
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||||
|
0, 0, nullptr, 0, nullptr, 1, &toTransfer);
|
||||||
|
|
||||||
|
VkClearColorValue clearColor = {{0.0f, 0.0f, 0.0f, 0.0f}};
|
||||||
|
VkImageSubresourceRange range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||||
|
vkCmdClearColorImage(cmd, sh.colorImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clearColor, 1, &range);
|
||||||
|
|
||||||
|
VkImageMemoryBarrier toRead = toTransfer;
|
||||||
|
toRead.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||||
|
toRead.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||||
|
toRead.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||||
|
toRead.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||||
|
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
||||||
|
0, 0, nullptr, 0, nullptr, 1, &toRead);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void WaterRenderer::shutdown() {
|
void WaterRenderer::shutdown() {
|
||||||
clear();
|
clear();
|
||||||
|
|
||||||
|
|
@ -304,13 +345,15 @@ VkDescriptorSet WaterRenderer::allocateMaterialSet() {
|
||||||
void WaterRenderer::destroySceneHistoryResources() {
|
void WaterRenderer::destroySceneHistoryResources() {
|
||||||
if (!vkCtx) return;
|
if (!vkCtx) return;
|
||||||
VkDevice device = vkCtx->getDevice();
|
VkDevice device = vkCtx->getDevice();
|
||||||
if (sceneColorView) { vkDestroyImageView(device, sceneColorView, nullptr); sceneColorView = VK_NULL_HANDLE; }
|
for (auto& sh : sceneHistory) {
|
||||||
if (sceneDepthView) { vkDestroyImageView(device, sceneDepthView, nullptr); sceneDepthView = VK_NULL_HANDLE; }
|
if (sh.colorView) { vkDestroyImageView(device, sh.colorView, nullptr); sh.colorView = VK_NULL_HANDLE; }
|
||||||
if (sceneColorImage) { vmaDestroyImage(vkCtx->getAllocator(), sceneColorImage, sceneColorAlloc); sceneColorImage = VK_NULL_HANDLE; sceneColorAlloc = VK_NULL_HANDLE; }
|
if (sh.depthView) { vkDestroyImageView(device, sh.depthView, nullptr); sh.depthView = VK_NULL_HANDLE; }
|
||||||
if (sceneDepthImage) { vmaDestroyImage(vkCtx->getAllocator(), sceneDepthImage, sceneDepthAlloc); sceneDepthImage = VK_NULL_HANDLE; sceneDepthAlloc = VK_NULL_HANDLE; }
|
if (sh.colorImage) { vmaDestroyImage(vkCtx->getAllocator(), sh.colorImage, sh.colorAlloc); sh.colorImage = VK_NULL_HANDLE; sh.colorAlloc = VK_NULL_HANDLE; }
|
||||||
|
if (sh.depthImage) { vmaDestroyImage(vkCtx->getAllocator(), sh.depthImage, sh.depthAlloc); sh.depthImage = VK_NULL_HANDLE; sh.depthAlloc = VK_NULL_HANDLE; }
|
||||||
|
sh.sceneSet = VK_NULL_HANDLE;
|
||||||
|
}
|
||||||
if (sceneColorSampler) { vkDestroySampler(device, sceneColorSampler, nullptr); sceneColorSampler = VK_NULL_HANDLE; }
|
if (sceneColorSampler) { vkDestroySampler(device, sceneColorSampler, nullptr); sceneColorSampler = VK_NULL_HANDLE; }
|
||||||
if (sceneDepthSampler) { vkDestroySampler(device, sceneDepthSampler, nullptr); sceneDepthSampler = VK_NULL_HANDLE; }
|
if (sceneDepthSampler) { vkDestroySampler(device, sceneDepthSampler, nullptr); sceneDepthSampler = VK_NULL_HANDLE; }
|
||||||
sceneSet = VK_NULL_HANDLE;
|
|
||||||
sceneHistoryExtent = {0, 0};
|
sceneHistoryExtent = {0, 0};
|
||||||
sceneHistoryReady = false;
|
sceneHistoryReady = false;
|
||||||
}
|
}
|
||||||
|
|
@ -323,54 +366,7 @@ void WaterRenderer::createSceneHistoryResources(VkExtent2D extent, VkFormat colo
|
||||||
vkResetDescriptorPool(device, sceneDescPool, 0);
|
vkResetDescriptorPool(device, sceneDescPool, 0);
|
||||||
sceneHistoryExtent = extent;
|
sceneHistoryExtent = extent;
|
||||||
|
|
||||||
VkImageCreateInfo colorImgInfo{};
|
// Create shared samplers
|
||||||
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{};
|
VkSamplerCreateInfo sampCI{};
|
||||||
sampCI.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
sampCI.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
||||||
sampCI.magFilter = VK_FILTER_LINEAR;
|
sampCI.magFilter = VK_FILTER_LINEAR;
|
||||||
|
|
@ -389,99 +385,155 @@ void WaterRenderer::createSceneHistoryResources(VkExtent2D extent, VkFormat colo
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
VkDescriptorSetAllocateInfo ai{};
|
VkImageCreateInfo colorImgInfo{};
|
||||||
ai.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
|
colorImgInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||||
ai.descriptorPool = sceneDescPool;
|
colorImgInfo.imageType = VK_IMAGE_TYPE_2D;
|
||||||
ai.descriptorSetCount = 1;
|
colorImgInfo.format = colorFormat;
|
||||||
ai.pSetLayouts = &sceneSetLayout;
|
colorImgInfo.extent = {extent.width, extent.height, 1};
|
||||||
if (vkAllocateDescriptorSets(device, &ai, &sceneSet) != VK_SUCCESS) {
|
colorImgInfo.mipLevels = 1;
|
||||||
LOG_ERROR("WaterRenderer: failed to allocate scene descriptor set");
|
colorImgInfo.arrayLayers = 1;
|
||||||
sceneSet = VK_NULL_HANDLE;
|
colorImgInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
return;
|
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;
|
||||||
|
|
||||||
|
VkImageCreateInfo depthImgInfo = colorImgInfo;
|
||||||
|
depthImgInfo.format = depthFormat;
|
||||||
|
|
||||||
|
VmaAllocationCreateInfo allocCI{};
|
||||||
|
allocCI.usage = VMA_MEMORY_USAGE_GPU_ONLY;
|
||||||
|
|
||||||
|
// Create per-frame images, views, and descriptor sets
|
||||||
|
for (uint32_t f = 0; f < SCENE_HISTORY_FRAMES; f++) {
|
||||||
|
auto& sh = sceneHistory[f];
|
||||||
|
|
||||||
|
if (vmaCreateImage(vkCtx->getAllocator(), &colorImgInfo, &allocCI, &sh.colorImage, &sh.colorAlloc, nullptr) != VK_SUCCESS) {
|
||||||
|
LOG_ERROR("WaterRenderer: failed to create scene color history image [", f, "]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (vmaCreateImage(vkCtx->getAllocator(), &depthImgInfo, &allocCI, &sh.depthImage, &sh.depthAlloc, nullptr) != VK_SUCCESS) {
|
||||||
|
LOG_ERROR("WaterRenderer: failed to create scene depth history image [", f, "]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkImageViewCreateInfo colorViewInfo{};
|
||||||
|
colorViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||||
|
colorViewInfo.image = sh.colorImage;
|
||||||
|
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, &sh.colorView) != VK_SUCCESS) {
|
||||||
|
LOG_ERROR("WaterRenderer: failed to create scene color history view [", f, "]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkImageViewCreateInfo depthViewInfo = colorViewInfo;
|
||||||
|
depthViewInfo.image = sh.depthImage;
|
||||||
|
depthViewInfo.format = depthFormat;
|
||||||
|
depthViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||||
|
if (vkCreateImageView(device, &depthViewInfo, nullptr, &sh.depthView) != VK_SUCCESS) {
|
||||||
|
LOG_ERROR("WaterRenderer: failed to create scene depth history view [", f, "]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate descriptor set for this frame
|
||||||
|
VkDescriptorSetAllocateInfo ai{};
|
||||||
|
ai.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
|
||||||
|
ai.descriptorPool = sceneDescPool;
|
||||||
|
ai.descriptorSetCount = 1;
|
||||||
|
ai.pSetLayouts = &sceneSetLayout;
|
||||||
|
if (vkAllocateDescriptorSets(device, &ai, &sh.sceneSet) != VK_SUCCESS) {
|
||||||
|
LOG_ERROR("WaterRenderer: failed to allocate scene descriptor set [", f, "]");
|
||||||
|
sh.sceneSet = VK_NULL_HANDLE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkDescriptorImageInfo colorInfo{};
|
||||||
|
colorInfo.sampler = sceneColorSampler;
|
||||||
|
colorInfo.imageView = sh.colorView;
|
||||||
|
colorInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||||
|
|
||||||
|
VkDescriptorImageInfo depthInfo{};
|
||||||
|
depthInfo.sampler = sceneDepthSampler;
|
||||||
|
depthInfo.imageView = sh.depthView;
|
||||||
|
depthInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||||
|
|
||||||
|
VkDescriptorImageInfo reflColorInfo{};
|
||||||
|
reflColorInfo.sampler = sceneColorSampler;
|
||||||
|
reflColorInfo.imageView = reflectionColorView ? reflectionColorView : sh.colorView;
|
||||||
|
reflColorInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||||
|
|
||||||
|
VkDescriptorBufferInfo reflUBOInfo{};
|
||||||
|
reflUBOInfo.buffer = reflectionUBO;
|
||||||
|
reflUBOInfo.offset = 0;
|
||||||
|
reflUBOInfo.range = sizeof(ReflectionUBOData);
|
||||||
|
|
||||||
|
std::vector<VkWriteDescriptorSet> writes;
|
||||||
|
|
||||||
|
VkWriteDescriptorSet w0{};
|
||||||
|
w0.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||||
|
w0.dstSet = sh.sceneSet;
|
||||||
|
w0.dstBinding = 0;
|
||||||
|
w0.descriptorCount = 1;
|
||||||
|
w0.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||||
|
w0.pImageInfo = &colorInfo;
|
||||||
|
writes.push_back(w0);
|
||||||
|
|
||||||
|
VkWriteDescriptorSet w1{};
|
||||||
|
w1.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||||
|
w1.dstSet = sh.sceneSet;
|
||||||
|
w1.dstBinding = 1;
|
||||||
|
w1.descriptorCount = 1;
|
||||||
|
w1.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||||
|
w1.pImageInfo = &depthInfo;
|
||||||
|
writes.push_back(w1);
|
||||||
|
|
||||||
|
VkWriteDescriptorSet w2{};
|
||||||
|
w2.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||||
|
w2.dstSet = sh.sceneSet;
|
||||||
|
w2.dstBinding = 2;
|
||||||
|
w2.descriptorCount = 1;
|
||||||
|
w2.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||||
|
w2.pImageInfo = &reflColorInfo;
|
||||||
|
writes.push_back(w2);
|
||||||
|
|
||||||
|
if (reflectionUBO) {
|
||||||
|
VkWriteDescriptorSet w3{};
|
||||||
|
w3.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||||
|
w3.dstSet = sh.sceneSet;
|
||||||
|
w3.dstBinding = 3;
|
||||||
|
w3.descriptorCount = 1;
|
||||||
|
w3.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||||
|
w3.pBufferInfo = &reflUBOInfo;
|
||||||
|
writes.push_back(w3);
|
||||||
|
}
|
||||||
|
|
||||||
|
vkUpdateDescriptorSets(device, static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
VkDescriptorImageInfo colorInfo{};
|
// Initialize all per-frame history images to shader-read layout
|
||||||
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;
|
|
||||||
|
|
||||||
// Reflection color texture (binding 2) — use scene color as placeholder until reflection is created
|
|
||||||
VkDescriptorImageInfo reflColorInfo{};
|
|
||||||
reflColorInfo.sampler = sceneColorSampler;
|
|
||||||
reflColorInfo.imageView = reflectionColorView ? reflectionColorView : sceneColorView;
|
|
||||||
reflColorInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
||||||
|
|
||||||
// Reflection UBO (binding 3)
|
|
||||||
VkDescriptorBufferInfo reflUBOInfo{};
|
|
||||||
reflUBOInfo.buffer = reflectionUBO;
|
|
||||||
reflUBOInfo.offset = 0;
|
|
||||||
reflUBOInfo.range = sizeof(ReflectionUBOData);
|
|
||||||
|
|
||||||
// Write bindings 0,1 always; write 2,3 only if reflection resources exist
|
|
||||||
std::vector<VkWriteDescriptorSet> writes;
|
|
||||||
|
|
||||||
VkWriteDescriptorSet w0{};
|
|
||||||
w0.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
||||||
w0.dstSet = sceneSet;
|
|
||||||
w0.dstBinding = 0;
|
|
||||||
w0.descriptorCount = 1;
|
|
||||||
w0.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
|
||||||
w0.pImageInfo = &colorInfo;
|
|
||||||
writes.push_back(w0);
|
|
||||||
|
|
||||||
VkWriteDescriptorSet w1{};
|
|
||||||
w1.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
||||||
w1.dstSet = sceneSet;
|
|
||||||
w1.dstBinding = 1;
|
|
||||||
w1.descriptorCount = 1;
|
|
||||||
w1.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
|
||||||
w1.pImageInfo = &depthInfo;
|
|
||||||
writes.push_back(w1);
|
|
||||||
|
|
||||||
VkWriteDescriptorSet w2{};
|
|
||||||
w2.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
||||||
w2.dstSet = sceneSet;
|
|
||||||
w2.dstBinding = 2;
|
|
||||||
w2.descriptorCount = 1;
|
|
||||||
w2.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
|
||||||
w2.pImageInfo = &reflColorInfo;
|
|
||||||
writes.push_back(w2);
|
|
||||||
|
|
||||||
if (reflectionUBO) {
|
|
||||||
VkWriteDescriptorSet w3{};
|
|
||||||
w3.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
||||||
w3.dstSet = sceneSet;
|
|
||||||
w3.dstBinding = 3;
|
|
||||||
w3.descriptorCount = 1;
|
|
||||||
w3.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
|
||||||
w3.pBufferInfo = &reflUBOInfo;
|
|
||||||
writes.push_back(w3);
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
vkCtx->immediateSubmit([&](VkCommandBuffer cmd) {
|
||||||
VkImageMemoryBarrier barriers[2]{};
|
std::vector<VkImageMemoryBarrier> barriers;
|
||||||
barriers[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
for (uint32_t f = 0; f < SCENE_HISTORY_FRAMES; f++) {
|
||||||
barriers[0].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
VkImageMemoryBarrier b{};
|
||||||
barriers[0].newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
b.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||||
barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
b.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
b.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||||
barriers[0].image = sceneColorImage;
|
b.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||||
barriers[0].subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||||
barriers[0].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
b.image = sceneHistory[f].colorImage;
|
||||||
|
b.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||||
barriers[1] = barriers[0];
|
b.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||||
barriers[1].image = sceneDepthImage;
|
barriers.push_back(b);
|
||||||
barriers[1].subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
|
||||||
|
|
||||||
|
b.image = sceneHistory[f].depthImage;
|
||||||
|
b.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||||
|
barriers.push_back(b);
|
||||||
|
}
|
||||||
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
||||||
0, 0, nullptr, 0, nullptr, 2, barriers);
|
0, 0, nullptr, 0, nullptr, static_cast<uint32_t>(barriers.size()), barriers.data());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -986,7 +1038,7 @@ void WaterRenderer::clear() {
|
||||||
// ==============================================================
|
// ==============================================================
|
||||||
|
|
||||||
void WaterRenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet,
|
void WaterRenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet,
|
||||||
const Camera& /*camera*/, float /*time*/, bool use1x) {
|
const Camera& /*camera*/, float /*time*/, bool use1x, uint32_t frameIndex) {
|
||||||
VkPipeline pipeline = (use1x && water1xPipeline) ? water1xPipeline : waterPipeline;
|
VkPipeline pipeline = (use1x && water1xPipeline) ? water1xPipeline : waterPipeline;
|
||||||
if (!renderingEnabled || surfaces.empty() || !pipeline) {
|
if (!renderingEnabled || surfaces.empty() || !pipeline) {
|
||||||
if (renderDiagCounter_++ % 300 == 0 && !surfaces.empty()) {
|
if (renderDiagCounter_++ % 300 == 0 && !surfaces.empty()) {
|
||||||
|
|
@ -997,7 +1049,9 @@ void WaterRenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet,
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!sceneSet) {
|
uint32_t fi = frameIndex % SCENE_HISTORY_FRAMES;
|
||||||
|
VkDescriptorSet activeSceneSet = sceneHistory[fi].sceneSet;
|
||||||
|
if (!activeSceneSet) {
|
||||||
if (renderDiagCounter_++ % 300 == 0) {
|
if (renderDiagCounter_++ % 300 == 0) {
|
||||||
LOG_WARNING("Water: render skipped — sceneSet is null, surfaces=", surfaces.size());
|
LOG_WARNING("Water: render skipped — sceneSet is null, surfaces=", surfaces.size());
|
||||||
}
|
}
|
||||||
|
|
@ -1009,7 +1063,7 @@ void WaterRenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet,
|
||||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout,
|
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout,
|
||||||
0, 1, &perFrameSet, 0, nullptr);
|
0, 1, &perFrameSet, 0, nullptr);
|
||||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout,
|
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout,
|
||||||
2, 1, &sceneSet, 0, nullptr);
|
2, 1, &activeSceneSet, 0, nullptr);
|
||||||
|
|
||||||
for (const auto& surface : surfaces) {
|
for (const auto& surface : surfaces) {
|
||||||
if (surface.vertexBuffer == VK_NULL_HANDLE || surface.indexCount == 0) continue;
|
if (surface.vertexBuffer == VK_NULL_HANDLE || surface.indexCount == 0) continue;
|
||||||
|
|
@ -1050,8 +1104,11 @@ void WaterRenderer::captureSceneHistory(VkCommandBuffer cmd,
|
||||||
VkImage srcColorImage,
|
VkImage srcColorImage,
|
||||||
VkImage srcDepthImage,
|
VkImage srcDepthImage,
|
||||||
VkExtent2D srcExtent,
|
VkExtent2D srcExtent,
|
||||||
bool srcDepthIsMsaa) {
|
bool srcDepthIsMsaa,
|
||||||
if (!vkCtx || !cmd || !sceneColorImage || !sceneDepthImage || srcExtent.width == 0 || srcExtent.height == 0) {
|
uint32_t frameIndex) {
|
||||||
|
uint32_t fi = frameIndex % SCENE_HISTORY_FRAMES;
|
||||||
|
auto& sh = sceneHistory[fi];
|
||||||
|
if (!vkCtx || !cmd || !sh.colorImage || !sh.depthImage || srcExtent.width == 0 || srcExtent.height == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1091,7 +1148,7 @@ void WaterRenderer::captureSceneHistory(VkCommandBuffer cmd,
|
||||||
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||||
0, VK_ACCESS_TRANSFER_READ_BIT,
|
0, VK_ACCESS_TRANSFER_READ_BIT,
|
||||||
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
|
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
|
||||||
barrier2(sceneColorImage, VK_IMAGE_ASPECT_COLOR_BIT,
|
barrier2(sh.colorImage, VK_IMAGE_ASPECT_COLOR_BIT,
|
||||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||||
VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
|
VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
|
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
|
||||||
|
|
@ -1101,9 +1158,9 @@ void WaterRenderer::captureSceneHistory(VkCommandBuffer cmd,
|
||||||
colorCopy.dstSubresource = {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};
|
colorCopy.extent = {copyExtent.width, copyExtent.height, 1};
|
||||||
vkCmdCopyImage(cmd, srcColorImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
vkCmdCopyImage(cmd, srcColorImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||||
sceneColorImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &colorCopy);
|
sh.colorImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &colorCopy);
|
||||||
|
|
||||||
barrier2(sceneColorImage, VK_IMAGE_ASPECT_COLOR_BIT,
|
barrier2(sh.colorImage, VK_IMAGE_ASPECT_COLOR_BIT,
|
||||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||||
VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
|
VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
|
||||||
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
|
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
|
||||||
|
|
@ -1118,7 +1175,7 @@ void WaterRenderer::captureSceneHistory(VkCommandBuffer cmd,
|
||||||
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
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_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT,
|
||||||
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
|
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
|
||||||
barrier2(sceneDepthImage, VK_IMAGE_ASPECT_DEPTH_BIT,
|
barrier2(sh.depthImage, VK_IMAGE_ASPECT_DEPTH_BIT,
|
||||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||||
VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
|
VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
|
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
|
||||||
|
|
@ -1128,9 +1185,9 @@ void WaterRenderer::captureSceneHistory(VkCommandBuffer cmd,
|
||||||
depthCopy.dstSubresource = {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};
|
depthCopy.extent = {copyExtent.width, copyExtent.height, 1};
|
||||||
vkCmdCopyImage(cmd, srcDepthImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
vkCmdCopyImage(cmd, srcDepthImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||||
sceneDepthImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &depthCopy);
|
sh.depthImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &depthCopy);
|
||||||
|
|
||||||
barrier2(sceneDepthImage, VK_IMAGE_ASPECT_DEPTH_BIT,
|
barrier2(sh.depthImage, VK_IMAGE_ASPECT_DEPTH_BIT,
|
||||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||||
VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
|
VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
|
||||||
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
|
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
|
||||||
|
|
@ -1543,11 +1600,11 @@ bool WaterRenderer::isWmoWaterAt(float glX, float glY) const {
|
||||||
glm::vec4 WaterRenderer::getLiquidColor(uint16_t liquidType) const {
|
glm::vec4 WaterRenderer::getLiquidColor(uint16_t liquidType) const {
|
||||||
uint8_t basicType = (liquidType == 0) ? 0 : ((liquidType - 1) % 4);
|
uint8_t basicType = (liquidType == 0) ? 0 : ((liquidType - 1) % 4);
|
||||||
switch (basicType) {
|
switch (basicType) {
|
||||||
case 0: return glm::vec4(0.12f, 0.32f, 0.48f, 1.0f); // inland: blue-green
|
case 0: return glm::vec4(0.10f, 0.28f, 0.55f, 1.0f); // inland: richer blue
|
||||||
case 1: return glm::vec4(0.04f, 0.14f, 0.30f, 1.0f); // ocean: deep blue
|
case 1: return glm::vec4(0.04f, 0.16f, 0.38f, 1.0f); // ocean: deep blue
|
||||||
case 2: return glm::vec4(0.9f, 0.3f, 0.05f, 1.0f); // magma
|
case 2: return glm::vec4(0.9f, 0.3f, 0.05f, 1.0f); // magma
|
||||||
case 3: return glm::vec4(0.2f, 0.6f, 0.1f, 1.0f); // slime
|
case 3: return glm::vec4(0.2f, 0.6f, 0.1f, 1.0f); // slime
|
||||||
default: return glm::vec4(0.12f, 0.32f, 0.48f, 1.0f);
|
default: return glm::vec4(0.10f, 0.28f, 0.55f, 1.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1815,21 +1872,28 @@ void WaterRenderer::endReflectionPass(VkCommandBuffer cmd) {
|
||||||
vkCmdEndRenderPass(cmd);
|
vkCmdEndRenderPass(cmd);
|
||||||
reflectionColorLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
reflectionColorLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||||
|
|
||||||
// Update scene descriptor set with the freshly rendered reflection texture
|
// Update all per-frame scene descriptor sets with the freshly rendered reflection texture
|
||||||
if (sceneSet && reflectionColorView && reflectionSampler) {
|
if (reflectionColorView && reflectionSampler) {
|
||||||
VkDescriptorImageInfo reflInfo{};
|
VkDescriptorImageInfo reflInfo{};
|
||||||
reflInfo.sampler = reflectionSampler;
|
reflInfo.sampler = reflectionSampler;
|
||||||
reflInfo.imageView = reflectionColorView;
|
reflInfo.imageView = reflectionColorView;
|
||||||
reflInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
reflInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||||
|
|
||||||
VkWriteDescriptorSet write{};
|
std::vector<VkWriteDescriptorSet> writes;
|
||||||
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
for (uint32_t f = 0; f < SCENE_HISTORY_FRAMES; f++) {
|
||||||
write.dstSet = sceneSet;
|
if (!sceneHistory[f].sceneSet) continue;
|
||||||
write.dstBinding = 2;
|
VkWriteDescriptorSet write{};
|
||||||
write.descriptorCount = 1;
|
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||||
write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
write.dstSet = sceneHistory[f].sceneSet;
|
||||||
write.pImageInfo = &reflInfo;
|
write.dstBinding = 2;
|
||||||
vkUpdateDescriptorSets(vkCtx->getDevice(), 1, &write, 0, nullptr);
|
write.descriptorCount = 1;
|
||||||
|
write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||||
|
write.pImageInfo = &reflInfo;
|
||||||
|
writes.push_back(write);
|
||||||
|
}
|
||||||
|
if (!writes.empty()) {
|
||||||
|
vkUpdateDescriptorSets(vkCtx->getDevice(), static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -288,6 +288,15 @@ void GameScreen::render(game::GameHandler& gameHandler) {
|
||||||
msaaSettingsApplied_ = true;
|
msaaSettingsApplied_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply saved water refraction setting once when renderer is available
|
||||||
|
if (!waterRefractionApplied_) {
|
||||||
|
auto* renderer = core::Application::getInstance().getRenderer();
|
||||||
|
if (renderer) {
|
||||||
|
renderer->setWaterRefractionEnabled(pendingWaterRefraction);
|
||||||
|
waterRefractionApplied_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Apply saved normal mapping / POM settings once when WMO renderer is available
|
// Apply saved normal mapping / POM settings once when WMO renderer is available
|
||||||
if (!normalMapSettingsApplied_) {
|
if (!normalMapSettingsApplied_) {
|
||||||
auto* renderer = core::Application::getInstance().getRenderer();
|
auto* renderer = core::Application::getInstance().getRenderer();
|
||||||
|
|
@ -6237,6 +6246,10 @@ void GameScreen::renderSettingsWindow() {
|
||||||
if (renderer) renderer->setShadowsEnabled(pendingShadows);
|
if (renderer) renderer->setShadowsEnabled(pendingShadows);
|
||||||
saveSettings();
|
saveSettings();
|
||||||
}
|
}
|
||||||
|
if (ImGui::Checkbox("Water Refraction", &pendingWaterRefraction)) {
|
||||||
|
if (renderer) renderer->setWaterRefractionEnabled(pendingWaterRefraction);
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
{
|
{
|
||||||
const char* aaLabels[] = { "Off", "2x MSAA", "4x MSAA", "8x MSAA" };
|
const char* aaLabels[] = { "Off", "2x MSAA", "4x MSAA", "8x MSAA" };
|
||||||
if (ImGui::Combo("Anti-Aliasing", &pendingAntiAliasing, aaLabels, 4)) {
|
if (ImGui::Combo("Anti-Aliasing", &pendingAntiAliasing, aaLabels, 4)) {
|
||||||
|
|
@ -6336,7 +6349,9 @@ void GameScreen::renderSettingsWindow() {
|
||||||
window->setFullscreen(pendingFullscreen);
|
window->setFullscreen(pendingFullscreen);
|
||||||
window->setVsync(pendingVsync);
|
window->setVsync(pendingVsync);
|
||||||
window->applyResolution(kResolutions[pendingResIndex][0], kResolutions[pendingResIndex][1]);
|
window->applyResolution(kResolutions[pendingResIndex][0], kResolutions[pendingResIndex][1]);
|
||||||
|
pendingWaterRefraction = false;
|
||||||
if (renderer) renderer->setShadowsEnabled(pendingShadows);
|
if (renderer) renderer->setShadowsEnabled(pendingShadows);
|
||||||
|
if (renderer) renderer->setWaterRefractionEnabled(pendingWaterRefraction);
|
||||||
if (renderer) renderer->setMsaaSamples(VK_SAMPLE_COUNT_1_BIT);
|
if (renderer) renderer->setMsaaSamples(VK_SAMPLE_COUNT_1_BIT);
|
||||||
if (renderer) {
|
if (renderer) {
|
||||||
if (auto* tm = renderer->getTerrainManager()) {
|
if (auto* tm = renderer->getTerrainManager()) {
|
||||||
|
|
@ -7349,6 +7364,7 @@ void GameScreen::saveSettings() {
|
||||||
out << "auto_loot=" << (pendingAutoLoot ? 1 : 0) << "\n";
|
out << "auto_loot=" << (pendingAutoLoot ? 1 : 0) << "\n";
|
||||||
out << "ground_clutter_density=" << pendingGroundClutterDensity << "\n";
|
out << "ground_clutter_density=" << pendingGroundClutterDensity << "\n";
|
||||||
out << "shadows=" << (pendingShadows ? 1 : 0) << "\n";
|
out << "shadows=" << (pendingShadows ? 1 : 0) << "\n";
|
||||||
|
out << "water_refraction=" << (pendingWaterRefraction ? 1 : 0) << "\n";
|
||||||
out << "antialiasing=" << pendingAntiAliasing << "\n";
|
out << "antialiasing=" << pendingAntiAliasing << "\n";
|
||||||
out << "normal_mapping=" << (pendingNormalMapping ? 1 : 0) << "\n";
|
out << "normal_mapping=" << (pendingNormalMapping ? 1 : 0) << "\n";
|
||||||
out << "normal_map_strength=" << pendingNormalMapStrength << "\n";
|
out << "normal_map_strength=" << pendingNormalMapStrength << "\n";
|
||||||
|
|
@ -7433,6 +7449,7 @@ void GameScreen::loadSettings() {
|
||||||
else if (key == "auto_loot") pendingAutoLoot = (std::stoi(val) != 0);
|
else if (key == "auto_loot") pendingAutoLoot = (std::stoi(val) != 0);
|
||||||
else if (key == "ground_clutter_density") pendingGroundClutterDensity = std::clamp(std::stoi(val), 0, 150);
|
else if (key == "ground_clutter_density") pendingGroundClutterDensity = std::clamp(std::stoi(val), 0, 150);
|
||||||
else if (key == "shadows") pendingShadows = (std::stoi(val) != 0);
|
else if (key == "shadows") pendingShadows = (std::stoi(val) != 0);
|
||||||
|
else if (key == "water_refraction") pendingWaterRefraction = (std::stoi(val) != 0);
|
||||||
else if (key == "antialiasing") pendingAntiAliasing = std::clamp(std::stoi(val), 0, 3);
|
else if (key == "antialiasing") pendingAntiAliasing = std::clamp(std::stoi(val), 0, 3);
|
||||||
else if (key == "normal_mapping") pendingNormalMapping = (std::stoi(val) != 0);
|
else if (key == "normal_mapping") pendingNormalMapping = (std::stoi(val) != 0);
|
||||||
else if (key == "normal_map_strength") pendingNormalMapStrength = std::clamp(std::stof(val), 0.0f, 2.0f);
|
else if (key == "normal_map_strength") pendingNormalMapStrength = std::clamp(std::stof(val), 0.0f, 2.0f);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue