mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-03 00:03:50 +00:00
Add configurable MSAA anti-aliasing, update auth screen and terrain shader
- MSAA: conditional 2-att (off) vs 3-att (on) render pass with auto-resolve - MSAA: multisampled color+depth images, query max supported sample count - MSAA: .setMultisample() on all 25+ main-pass pipelines across 17 renderers - MSAA: recreatePipelines() on every sub-renderer for runtime MSAA changes - MSAA: Renderer::setMsaaSamples() orchestrates swapchain+pipeline+ImGui rebuild - MSAA: Anti-Aliasing combo (Off/2x/4x/8x) in Video settings, persisted - Update auth screen assets and terrain fragment shader
This commit is contained in:
parent
6d213ad49b
commit
e12141a673
54 changed files with 2069 additions and 144 deletions
|
|
@ -86,6 +86,7 @@ bool Celestial::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout)
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL) // test on, write off (sky layer)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(vkCtx_->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
|
|
@ -106,6 +107,71 @@ bool Celestial::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout)
|
|||
return true;
|
||||
}
|
||||
|
||||
void Celestial::recreatePipelines() {
|
||||
if (!vkCtx_) return;
|
||||
VkDevice device = vkCtx_->getDevice();
|
||||
|
||||
if (pipeline_ != VK_NULL_HANDLE) { vkDestroyPipeline(device, pipeline_, nullptr); pipeline_ = VK_NULL_HANDLE; }
|
||||
|
||||
VkShaderModule vertModule;
|
||||
if (!vertModule.loadFromFile(device, "assets/shaders/celestial.vert.spv")) {
|
||||
LOG_ERROR("Celestial::recreatePipelines: failed to load vertex shader");
|
||||
return;
|
||||
}
|
||||
VkShaderModule fragModule;
|
||||
if (!fragModule.loadFromFile(device, "assets/shaders/celestial.frag.spv")) {
|
||||
LOG_ERROR("Celestial::recreatePipelines: failed to load fragment shader");
|
||||
vertModule.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
|
||||
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
// Vertex input (same as initialize)
|
||||
VkVertexInputBindingDescription binding{};
|
||||
binding.binding = 0;
|
||||
binding.stride = 5 * sizeof(float);
|
||||
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
VkVertexInputAttributeDescription posAttr{};
|
||||
posAttr.location = 0;
|
||||
posAttr.binding = 0;
|
||||
posAttr.format = VK_FORMAT_R32G32B32_SFLOAT;
|
||||
posAttr.offset = 0;
|
||||
|
||||
VkVertexInputAttributeDescription uvAttr{};
|
||||
uvAttr.location = 1;
|
||||
uvAttr.binding = 0;
|
||||
uvAttr.format = VK_FORMAT_R32G32_SFLOAT;
|
||||
uvAttr.offset = 3 * sizeof(float);
|
||||
|
||||
std::vector<VkDynamicState> dynamicStates = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR
|
||||
};
|
||||
|
||||
pipeline_ = PipelineBuilder()
|
||||
.setShaders(vertStage, fragStage)
|
||||
.setVertexInput({binding}, {posAttr, uvAttr})
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(vkCtx_->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
.build(device);
|
||||
|
||||
vertModule.destroy();
|
||||
fragModule.destroy();
|
||||
|
||||
if (pipeline_ == VK_NULL_HANDLE) {
|
||||
LOG_ERROR("Celestial::recreatePipelines: failed to create pipeline");
|
||||
}
|
||||
}
|
||||
|
||||
void Celestial::shutdown() {
|
||||
destroyQuad();
|
||||
|
||||
|
|
|
|||
|
|
@ -210,6 +210,7 @@ bool CharacterRenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFram
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, depthWrite, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(blendState)
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
|
||||
|
|
@ -2564,5 +2565,69 @@ void CharacterRenderer::dumpAnimations(uint32_t instanceId) const {
|
|||
core::Logger::getInstance().info("=== End animation dump ===");
|
||||
}
|
||||
|
||||
void CharacterRenderer::recreatePipelines() {
|
||||
if (!vkCtx_) return;
|
||||
VkDevice device = vkCtx_->getDevice();
|
||||
vkDeviceWaitIdle(device);
|
||||
|
||||
// Destroy old main-pass pipelines (NOT shadow, NOT pipeline layout)
|
||||
if (opaquePipeline_) { vkDestroyPipeline(device, opaquePipeline_, nullptr); opaquePipeline_ = VK_NULL_HANDLE; }
|
||||
if (alphaTestPipeline_) { vkDestroyPipeline(device, alphaTestPipeline_, nullptr); alphaTestPipeline_ = VK_NULL_HANDLE; }
|
||||
if (alphaPipeline_) { vkDestroyPipeline(device, alphaPipeline_, nullptr); alphaPipeline_ = VK_NULL_HANDLE; }
|
||||
if (additivePipeline_) { vkDestroyPipeline(device, additivePipeline_, nullptr); additivePipeline_ = VK_NULL_HANDLE; }
|
||||
|
||||
// --- Load shaders ---
|
||||
rendering::VkShaderModule charVert, charFrag;
|
||||
charVert.loadFromFile(device, "assets/shaders/character.vert.spv");
|
||||
charFrag.loadFromFile(device, "assets/shaders/character.frag.spv");
|
||||
|
||||
if (!charVert.isValid() || !charFrag.isValid()) {
|
||||
LOG_ERROR("CharacterRenderer::recreatePipelines: missing required shaders");
|
||||
return;
|
||||
}
|
||||
|
||||
VkRenderPass mainPass = vkCtx_->getImGuiRenderPass();
|
||||
|
||||
// --- Vertex input ---
|
||||
VkVertexInputBindingDescription charBinding{};
|
||||
charBinding.binding = 0;
|
||||
charBinding.stride = sizeof(pipeline::M2Vertex);
|
||||
charBinding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
std::vector<VkVertexInputAttributeDescription> charAttrs = {
|
||||
{0, 0, VK_FORMAT_R32G32B32_SFLOAT, static_cast<uint32_t>(offsetof(pipeline::M2Vertex, position))},
|
||||
{1, 0, VK_FORMAT_R8G8B8A8_UNORM, static_cast<uint32_t>(offsetof(pipeline::M2Vertex, boneWeights))},
|
||||
{2, 0, VK_FORMAT_R8G8B8A8_UINT, static_cast<uint32_t>(offsetof(pipeline::M2Vertex, boneIndices))},
|
||||
{3, 0, VK_FORMAT_R32G32B32_SFLOAT, static_cast<uint32_t>(offsetof(pipeline::M2Vertex, normal))},
|
||||
{4, 0, VK_FORMAT_R32G32_SFLOAT, static_cast<uint32_t>(offsetof(pipeline::M2Vertex, texCoords))},
|
||||
};
|
||||
|
||||
auto buildCharPipeline = [&](VkPipelineColorBlendAttachmentState blendState, bool depthWrite) -> VkPipeline {
|
||||
return PipelineBuilder()
|
||||
.setShaders(charVert.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
|
||||
charFrag.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT))
|
||||
.setVertexInput({charBinding}, charAttrs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, depthWrite, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(blendState)
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
|
||||
.build(device);
|
||||
};
|
||||
|
||||
opaquePipeline_ = buildCharPipeline(PipelineBuilder::blendDisabled(), true);
|
||||
alphaTestPipeline_ = buildCharPipeline(PipelineBuilder::blendAlpha(), true);
|
||||
alphaPipeline_ = buildCharPipeline(PipelineBuilder::blendAlpha(), false);
|
||||
additivePipeline_ = buildCharPipeline(PipelineBuilder::blendAdditive(), false);
|
||||
|
||||
charVert.destroy();
|
||||
charFrag.destroy();
|
||||
|
||||
core::Logger::getInstance().info("CharacterRenderer: pipelines recreated");
|
||||
}
|
||||
|
||||
} // namespace rendering
|
||||
} // namespace wowee
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ bool ChargeEffect::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayo
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAdditive()) // Additive blend for fiery glow
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(ribbonPipelineLayout_)
|
||||
.setRenderPass(vkCtx_->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
|
|
@ -160,6 +161,7 @@ bool ChargeEffect::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayo
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(dustPipelineLayout_)
|
||||
.setRenderPass(vkCtx_->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
|
|
@ -249,6 +251,122 @@ void ChargeEffect::shutdown() {
|
|||
dustPuffs_.clear();
|
||||
}
|
||||
|
||||
void ChargeEffect::recreatePipelines() {
|
||||
if (!vkCtx_) return;
|
||||
VkDevice device = vkCtx_->getDevice();
|
||||
|
||||
// Destroy old pipelines (NOT layouts)
|
||||
if (ribbonPipeline_ != VK_NULL_HANDLE) {
|
||||
vkDestroyPipeline(device, ribbonPipeline_, nullptr);
|
||||
ribbonPipeline_ = VK_NULL_HANDLE;
|
||||
}
|
||||
if (dustPipeline_ != VK_NULL_HANDLE) {
|
||||
vkDestroyPipeline(device, dustPipeline_, nullptr);
|
||||
dustPipeline_ = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
std::vector<VkDynamicState> dynamicStates = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR
|
||||
};
|
||||
|
||||
// ---- Rebuild ribbon trail pipeline (TRIANGLE_STRIP) ----
|
||||
{
|
||||
VkShaderModule vertModule;
|
||||
vertModule.loadFromFile(device, "assets/shaders/charge_ribbon.vert.spv");
|
||||
VkShaderModule fragModule;
|
||||
fragModule.loadFromFile(device, "assets/shaders/charge_ribbon.frag.spv");
|
||||
|
||||
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
|
||||
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
VkVertexInputBindingDescription binding{};
|
||||
binding.binding = 0;
|
||||
binding.stride = 6 * sizeof(float);
|
||||
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
std::vector<VkVertexInputAttributeDescription> attrs(4);
|
||||
attrs[0].location = 0;
|
||||
attrs[0].binding = 0;
|
||||
attrs[0].format = VK_FORMAT_R32G32B32_SFLOAT;
|
||||
attrs[0].offset = 0;
|
||||
attrs[1].location = 1;
|
||||
attrs[1].binding = 0;
|
||||
attrs[1].format = VK_FORMAT_R32_SFLOAT;
|
||||
attrs[1].offset = 3 * sizeof(float);
|
||||
attrs[2].location = 2;
|
||||
attrs[2].binding = 0;
|
||||
attrs[2].format = VK_FORMAT_R32_SFLOAT;
|
||||
attrs[2].offset = 4 * sizeof(float);
|
||||
attrs[3].location = 3;
|
||||
attrs[3].binding = 0;
|
||||
attrs[3].format = VK_FORMAT_R32_SFLOAT;
|
||||
attrs[3].offset = 5 * sizeof(float);
|
||||
|
||||
ribbonPipeline_ = PipelineBuilder()
|
||||
.setShaders(vertStage, fragStage)
|
||||
.setVertexInput({binding}, attrs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAdditive())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(ribbonPipelineLayout_)
|
||||
.setRenderPass(vkCtx_->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
.build(device);
|
||||
|
||||
vertModule.destroy();
|
||||
fragModule.destroy();
|
||||
}
|
||||
|
||||
// ---- Rebuild dust puff pipeline (POINT_LIST) ----
|
||||
{
|
||||
VkShaderModule vertModule;
|
||||
vertModule.loadFromFile(device, "assets/shaders/charge_dust.vert.spv");
|
||||
VkShaderModule fragModule;
|
||||
fragModule.loadFromFile(device, "assets/shaders/charge_dust.frag.spv");
|
||||
|
||||
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
|
||||
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
VkVertexInputBindingDescription binding{};
|
||||
binding.binding = 0;
|
||||
binding.stride = 5 * sizeof(float);
|
||||
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
std::vector<VkVertexInputAttributeDescription> attrs(3);
|
||||
attrs[0].location = 0;
|
||||
attrs[0].binding = 0;
|
||||
attrs[0].format = VK_FORMAT_R32G32B32_SFLOAT;
|
||||
attrs[0].offset = 0;
|
||||
attrs[1].location = 1;
|
||||
attrs[1].binding = 0;
|
||||
attrs[1].format = VK_FORMAT_R32_SFLOAT;
|
||||
attrs[1].offset = 3 * sizeof(float);
|
||||
attrs[2].location = 2;
|
||||
attrs[2].binding = 0;
|
||||
attrs[2].format = VK_FORMAT_R32_SFLOAT;
|
||||
attrs[2].offset = 4 * sizeof(float);
|
||||
|
||||
dustPipeline_ = PipelineBuilder()
|
||||
.setShaders(vertStage, fragStage)
|
||||
.setVertexInput({binding}, attrs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_POINT_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(dustPipelineLayout_)
|
||||
.setRenderPass(vkCtx_->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
.build(device);
|
||||
|
||||
vertModule.destroy();
|
||||
fragModule.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
void ChargeEffect::tryLoadM2Models(M2Renderer* m2Renderer, pipeline::AssetManager* assets) {
|
||||
if (!m2Renderer || !assets) return;
|
||||
m2Renderer_ = m2Renderer;
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ bool Clouds::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) {
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL) // test on, write off (sky layer)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(vkCtx_->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
|
|
@ -100,6 +101,65 @@ bool Clouds::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void Clouds::recreatePipelines() {
|
||||
if (!vkCtx_) return;
|
||||
VkDevice device = vkCtx_->getDevice();
|
||||
|
||||
if (pipeline_ != VK_NULL_HANDLE) { vkDestroyPipeline(device, pipeline_, nullptr); pipeline_ = VK_NULL_HANDLE; }
|
||||
|
||||
VkShaderModule vertModule;
|
||||
if (!vertModule.loadFromFile(device, "assets/shaders/clouds.vert.spv")) {
|
||||
LOG_ERROR("Clouds::recreatePipelines: failed to load vertex shader");
|
||||
return;
|
||||
}
|
||||
VkShaderModule fragModule;
|
||||
if (!fragModule.loadFromFile(device, "assets/shaders/clouds.frag.spv")) {
|
||||
LOG_ERROR("Clouds::recreatePipelines: failed to load fragment shader");
|
||||
vertModule.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
|
||||
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
// Vertex input (same as initialize)
|
||||
VkVertexInputBindingDescription binding{};
|
||||
binding.binding = 0;
|
||||
binding.stride = sizeof(glm::vec3);
|
||||
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
VkVertexInputAttributeDescription posAttr{};
|
||||
posAttr.location = 0;
|
||||
posAttr.binding = 0;
|
||||
posAttr.format = VK_FORMAT_R32G32B32_SFLOAT;
|
||||
posAttr.offset = 0;
|
||||
|
||||
std::vector<VkDynamicState> dynamicStates = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR
|
||||
};
|
||||
|
||||
pipeline_ = PipelineBuilder()
|
||||
.setShaders(vertStage, fragStage)
|
||||
.setVertexInput({binding}, {posAttr})
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(vkCtx_->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
.build(device);
|
||||
|
||||
vertModule.destroy();
|
||||
fragModule.destroy();
|
||||
|
||||
if (pipeline_ == VK_NULL_HANDLE) {
|
||||
LOG_ERROR("Clouds::recreatePipelines: failed to create pipeline");
|
||||
}
|
||||
}
|
||||
|
||||
void Clouds::shutdown() {
|
||||
destroyBuffers();
|
||||
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ bool LensFlare::initialize(VkContext* ctx, VkDescriptorSetLayout /*perFrameLayou
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setNoDepthTest()
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAdditive())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(pipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
|
|
@ -146,6 +147,63 @@ void LensFlare::shutdown() {
|
|||
vkCtx = nullptr;
|
||||
}
|
||||
|
||||
void LensFlare::recreatePipelines() {
|
||||
if (!vkCtx) return;
|
||||
VkDevice device = vkCtx->getDevice();
|
||||
|
||||
// Destroy old pipeline (NOT layout)
|
||||
if (pipeline != VK_NULL_HANDLE) {
|
||||
vkDestroyPipeline(device, pipeline, nullptr);
|
||||
pipeline = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VkShaderModule vertModule;
|
||||
vertModule.loadFromFile(device, "assets/shaders/lens_flare.vert.spv");
|
||||
VkShaderModule fragModule;
|
||||
fragModule.loadFromFile(device, "assets/shaders/lens_flare.frag.spv");
|
||||
|
||||
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
|
||||
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
VkVertexInputBindingDescription binding{};
|
||||
binding.binding = 0;
|
||||
binding.stride = 4 * sizeof(float);
|
||||
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
VkVertexInputAttributeDescription posAttr{};
|
||||
posAttr.location = 0;
|
||||
posAttr.binding = 0;
|
||||
posAttr.format = VK_FORMAT_R32G32_SFLOAT;
|
||||
posAttr.offset = 0;
|
||||
|
||||
VkVertexInputAttributeDescription uvAttr{};
|
||||
uvAttr.location = 1;
|
||||
uvAttr.binding = 0;
|
||||
uvAttr.format = VK_FORMAT_R32G32_SFLOAT;
|
||||
uvAttr.offset = 2 * sizeof(float);
|
||||
|
||||
std::vector<VkDynamicState> dynamicStates = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR
|
||||
};
|
||||
|
||||
pipeline = PipelineBuilder()
|
||||
.setShaders(vertStage, fragStage)
|
||||
.setVertexInput({binding}, {posAttr, uvAttr})
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setNoDepthTest()
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAdditive())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(pipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
.build(device);
|
||||
|
||||
vertModule.destroy();
|
||||
fragModule.destroy();
|
||||
}
|
||||
|
||||
void LensFlare::generateFlareElements() {
|
||||
flareElements.clear();
|
||||
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ bool Lightning::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout)
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setNoDepthTest() // Always visible (like the GL version)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAdditive()) // Additive for electric glow
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(boltPipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
|
|
@ -164,6 +165,7 @@ bool Lightning::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout)
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setNoDepthTest()
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(flashPipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
|
|
@ -253,6 +255,102 @@ void Lightning::shutdown() {
|
|||
vkCtx = nullptr;
|
||||
}
|
||||
|
||||
void Lightning::recreatePipelines() {
|
||||
if (!vkCtx) return;
|
||||
VkDevice device = vkCtx->getDevice();
|
||||
|
||||
// Destroy old pipelines (NOT layouts)
|
||||
if (boltPipeline != VK_NULL_HANDLE) {
|
||||
vkDestroyPipeline(device, boltPipeline, nullptr);
|
||||
boltPipeline = VK_NULL_HANDLE;
|
||||
}
|
||||
if (flashPipeline != VK_NULL_HANDLE) {
|
||||
vkDestroyPipeline(device, flashPipeline, nullptr);
|
||||
flashPipeline = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
std::vector<VkDynamicState> dynamicStates = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR
|
||||
};
|
||||
|
||||
// ---- Rebuild bolt pipeline (LINE_STRIP) ----
|
||||
{
|
||||
VkShaderModule vertModule;
|
||||
vertModule.loadFromFile(device, "assets/shaders/lightning_bolt.vert.spv");
|
||||
VkShaderModule fragModule;
|
||||
fragModule.loadFromFile(device, "assets/shaders/lightning_bolt.frag.spv");
|
||||
|
||||
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
|
||||
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
VkVertexInputBindingDescription binding{};
|
||||
binding.binding = 0;
|
||||
binding.stride = sizeof(glm::vec3);
|
||||
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
VkVertexInputAttributeDescription posAttr{};
|
||||
posAttr.location = 0;
|
||||
posAttr.binding = 0;
|
||||
posAttr.format = VK_FORMAT_R32G32B32_SFLOAT;
|
||||
posAttr.offset = 0;
|
||||
|
||||
boltPipeline = PipelineBuilder()
|
||||
.setShaders(vertStage, fragStage)
|
||||
.setVertexInput({binding}, {posAttr})
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_LINE_STRIP)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setNoDepthTest()
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAdditive())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(boltPipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
.build(device);
|
||||
|
||||
vertModule.destroy();
|
||||
fragModule.destroy();
|
||||
}
|
||||
|
||||
// ---- Rebuild flash pipeline (TRIANGLE_STRIP) ----
|
||||
{
|
||||
VkShaderModule vertModule;
|
||||
vertModule.loadFromFile(device, "assets/shaders/lightning_flash.vert.spv");
|
||||
VkShaderModule fragModule;
|
||||
fragModule.loadFromFile(device, "assets/shaders/lightning_flash.frag.spv");
|
||||
|
||||
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
|
||||
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
VkVertexInputBindingDescription binding{};
|
||||
binding.binding = 0;
|
||||
binding.stride = 2 * sizeof(float);
|
||||
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
VkVertexInputAttributeDescription posAttr{};
|
||||
posAttr.location = 0;
|
||||
posAttr.binding = 0;
|
||||
posAttr.format = VK_FORMAT_R32G32_SFLOAT;
|
||||
posAttr.offset = 0;
|
||||
|
||||
flashPipeline = PipelineBuilder()
|
||||
.setShaders(vertStage, fragStage)
|
||||
.setVertexInput({binding}, {posAttr})
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setNoDepthTest()
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(flashPipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
.build(device);
|
||||
|
||||
vertModule.destroy();
|
||||
fragModule.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
void Lightning::update(float deltaTime, const Camera& camera) {
|
||||
if (!enabled) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -16,8 +16,7 @@ namespace wowee {
|
|||
namespace rendering {
|
||||
|
||||
LoadingScreen::LoadingScreen() {
|
||||
imagePaths.push_back("assets/loading1.jpeg");
|
||||
imagePaths.push_back("assets/loading2.jpeg");
|
||||
imagePaths.push_back("assets/krayonload.png");
|
||||
}
|
||||
|
||||
LoadingScreen::~LoadingScreen() {
|
||||
|
|
|
|||
|
|
@ -468,6 +468,7 @@ bool M2Renderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, depthWrite, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(blendState)
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
|
||||
|
|
@ -502,6 +503,7 @@ bool M2Renderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(blend)
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(particlePipelineLayout_)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
|
||||
|
|
@ -534,6 +536,7 @@ bool M2Renderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(smokePipelineLayout_)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
|
||||
|
|
@ -3677,5 +3680,144 @@ float M2Renderer::raycastBoundingBoxes(const glm::vec3& origin, const glm::vec3&
|
|||
return closestHit;
|
||||
}
|
||||
|
||||
void M2Renderer::recreatePipelines() {
|
||||
if (!vkCtx_) return;
|
||||
VkDevice device = vkCtx_->getDevice();
|
||||
vkDeviceWaitIdle(device);
|
||||
|
||||
// Destroy old main-pass pipelines (NOT shadow, NOT pipeline layouts)
|
||||
if (opaquePipeline_) { vkDestroyPipeline(device, opaquePipeline_, nullptr); opaquePipeline_ = VK_NULL_HANDLE; }
|
||||
if (alphaTestPipeline_) { vkDestroyPipeline(device, alphaTestPipeline_, nullptr); alphaTestPipeline_ = VK_NULL_HANDLE; }
|
||||
if (alphaPipeline_) { vkDestroyPipeline(device, alphaPipeline_, nullptr); alphaPipeline_ = VK_NULL_HANDLE; }
|
||||
if (additivePipeline_) { vkDestroyPipeline(device, additivePipeline_, nullptr); additivePipeline_ = VK_NULL_HANDLE; }
|
||||
if (particlePipeline_) { vkDestroyPipeline(device, particlePipeline_, nullptr); particlePipeline_ = VK_NULL_HANDLE; }
|
||||
if (particleAdditivePipeline_) { vkDestroyPipeline(device, particleAdditivePipeline_, nullptr); particleAdditivePipeline_ = VK_NULL_HANDLE; }
|
||||
if (smokePipeline_) { vkDestroyPipeline(device, smokePipeline_, nullptr); smokePipeline_ = VK_NULL_HANDLE; }
|
||||
|
||||
// --- Load shaders ---
|
||||
rendering::VkShaderModule m2Vert, m2Frag;
|
||||
rendering::VkShaderModule particleVert, particleFrag;
|
||||
rendering::VkShaderModule smokeVert, smokeFrag;
|
||||
|
||||
m2Vert.loadFromFile(device, "assets/shaders/m2.vert.spv");
|
||||
m2Frag.loadFromFile(device, "assets/shaders/m2.frag.spv");
|
||||
particleVert.loadFromFile(device, "assets/shaders/m2_particle.vert.spv");
|
||||
particleFrag.loadFromFile(device, "assets/shaders/m2_particle.frag.spv");
|
||||
smokeVert.loadFromFile(device, "assets/shaders/m2_smoke.vert.spv");
|
||||
smokeFrag.loadFromFile(device, "assets/shaders/m2_smoke.frag.spv");
|
||||
|
||||
if (!m2Vert.isValid() || !m2Frag.isValid()) {
|
||||
LOG_ERROR("M2Renderer::recreatePipelines: missing required shaders");
|
||||
return;
|
||||
}
|
||||
|
||||
VkRenderPass mainPass = vkCtx_->getImGuiRenderPass();
|
||||
|
||||
// --- M2 model vertex input ---
|
||||
VkVertexInputBindingDescription m2Binding{};
|
||||
m2Binding.binding = 0;
|
||||
m2Binding.stride = 18 * sizeof(float);
|
||||
m2Binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
std::vector<VkVertexInputAttributeDescription> m2Attrs = {
|
||||
{0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0}, // position
|
||||
{1, 0, VK_FORMAT_R32G32B32_SFLOAT, 3 * sizeof(float)}, // normal
|
||||
{2, 0, VK_FORMAT_R32G32_SFLOAT, 6 * sizeof(float)}, // texCoord0
|
||||
{5, 0, VK_FORMAT_R32G32_SFLOAT, 8 * sizeof(float)}, // texCoord1
|
||||
{3, 0, VK_FORMAT_R32G32B32A32_SFLOAT, 10 * sizeof(float)}, // boneWeights
|
||||
{4, 0, VK_FORMAT_R32G32B32A32_SFLOAT, 14 * sizeof(float)}, // boneIndices (float)
|
||||
};
|
||||
|
||||
auto buildM2Pipeline = [&](VkPipelineColorBlendAttachmentState blendState, bool depthWrite) -> VkPipeline {
|
||||
return PipelineBuilder()
|
||||
.setShaders(m2Vert.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
|
||||
m2Frag.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT))
|
||||
.setVertexInput({m2Binding}, m2Attrs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, depthWrite, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(blendState)
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
|
||||
.build(device);
|
||||
};
|
||||
|
||||
opaquePipeline_ = buildM2Pipeline(PipelineBuilder::blendDisabled(), true);
|
||||
alphaTestPipeline_ = buildM2Pipeline(PipelineBuilder::blendAlpha(), true);
|
||||
alphaPipeline_ = buildM2Pipeline(PipelineBuilder::blendAlpha(), false);
|
||||
additivePipeline_ = buildM2Pipeline(PipelineBuilder::blendAdditive(), false);
|
||||
|
||||
// --- Particle pipelines ---
|
||||
if (particleVert.isValid() && particleFrag.isValid()) {
|
||||
VkVertexInputBindingDescription pBind{};
|
||||
pBind.binding = 0;
|
||||
pBind.stride = 9 * sizeof(float); // pos3 + color4 + size1 + tile1
|
||||
pBind.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
std::vector<VkVertexInputAttributeDescription> pAttrs = {
|
||||
{0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0}, // position
|
||||
{1, 0, VK_FORMAT_R32G32B32A32_SFLOAT, 3 * sizeof(float)}, // color
|
||||
{2, 0, VK_FORMAT_R32_SFLOAT, 7 * sizeof(float)}, // size
|
||||
{3, 0, VK_FORMAT_R32_SFLOAT, 8 * sizeof(float)}, // tile
|
||||
};
|
||||
|
||||
auto buildParticlePipeline = [&](VkPipelineColorBlendAttachmentState blend) -> VkPipeline {
|
||||
return PipelineBuilder()
|
||||
.setShaders(particleVert.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
|
||||
particleFrag.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT))
|
||||
.setVertexInput({pBind}, pAttrs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_POINT_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(blend)
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(particlePipelineLayout_)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
|
||||
.build(device);
|
||||
};
|
||||
|
||||
particlePipeline_ = buildParticlePipeline(PipelineBuilder::blendAlpha());
|
||||
particleAdditivePipeline_ = buildParticlePipeline(PipelineBuilder::blendAdditive());
|
||||
}
|
||||
|
||||
// --- Smoke pipeline ---
|
||||
if (smokeVert.isValid() && smokeFrag.isValid()) {
|
||||
VkVertexInputBindingDescription sBind{};
|
||||
sBind.binding = 0;
|
||||
sBind.stride = 6 * sizeof(float); // pos3 + lifeRatio1 + size1 + isSpark1
|
||||
sBind.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
std::vector<VkVertexInputAttributeDescription> sAttrs = {
|
||||
{0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0}, // position
|
||||
{1, 0, VK_FORMAT_R32_SFLOAT, 3 * sizeof(float)}, // lifeRatio
|
||||
{2, 0, VK_FORMAT_R32_SFLOAT, 4 * sizeof(float)}, // size
|
||||
{3, 0, VK_FORMAT_R32_SFLOAT, 5 * sizeof(float)}, // isSpark
|
||||
};
|
||||
|
||||
smokePipeline_ = PipelineBuilder()
|
||||
.setShaders(smokeVert.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
|
||||
smokeFrag.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT))
|
||||
.setVertexInput({sBind}, sAttrs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_POINT_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(smokePipelineLayout_)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
|
||||
.build(device);
|
||||
}
|
||||
|
||||
m2Vert.destroy(); m2Frag.destroy();
|
||||
particleVert.destroy(); particleFrag.destroy();
|
||||
smokeVert.destroy(); smokeFrag.destroy();
|
||||
|
||||
core::Logger::getInstance().info("M2Renderer: pipelines recreated");
|
||||
}
|
||||
|
||||
} // namespace rendering
|
||||
} // namespace wowee
|
||||
|
|
|
|||
|
|
@ -446,7 +446,7 @@ void Minimap::render(VkCommandBuffer cmd, const Camera& playerCamera,
|
|||
float pixelW = static_cast<float>(mapSize) / screenWidth;
|
||||
float pixelH = static_cast<float>(mapSize) / screenHeight;
|
||||
float x = 1.0f - pixelW - margin / screenWidth;
|
||||
float y = 1.0f - pixelH - margin / screenHeight;
|
||||
float y = margin / screenHeight; // top edge in Vulkan (y=0 is top)
|
||||
|
||||
// Compute player's UV in the composite texture
|
||||
constexpr float TILE_SIZE = core::coords::TILE_SIZE;
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ bool MountDust::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout)
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(pipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
|
|
@ -145,6 +146,65 @@ void MountDust::shutdown() {
|
|||
particles.clear();
|
||||
}
|
||||
|
||||
void MountDust::recreatePipelines() {
|
||||
if (!vkCtx) return;
|
||||
VkDevice device = vkCtx->getDevice();
|
||||
|
||||
// Destroy old pipeline (NOT layout)
|
||||
if (pipeline != VK_NULL_HANDLE) {
|
||||
vkDestroyPipeline(device, pipeline, nullptr);
|
||||
pipeline = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VkShaderModule vertModule;
|
||||
vertModule.loadFromFile(device, "assets/shaders/mount_dust.vert.spv");
|
||||
VkShaderModule fragModule;
|
||||
fragModule.loadFromFile(device, "assets/shaders/mount_dust.frag.spv");
|
||||
|
||||
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
|
||||
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
VkVertexInputBindingDescription binding{};
|
||||
binding.binding = 0;
|
||||
binding.stride = 5 * sizeof(float);
|
||||
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
std::vector<VkVertexInputAttributeDescription> attrs(3);
|
||||
attrs[0].location = 0;
|
||||
attrs[0].binding = 0;
|
||||
attrs[0].format = VK_FORMAT_R32G32B32_SFLOAT;
|
||||
attrs[0].offset = 0;
|
||||
attrs[1].location = 1;
|
||||
attrs[1].binding = 0;
|
||||
attrs[1].format = VK_FORMAT_R32_SFLOAT;
|
||||
attrs[1].offset = 3 * sizeof(float);
|
||||
attrs[2].location = 2;
|
||||
attrs[2].binding = 0;
|
||||
attrs[2].format = VK_FORMAT_R32_SFLOAT;
|
||||
attrs[2].offset = 4 * sizeof(float);
|
||||
|
||||
std::vector<VkDynamicState> dynamicStates = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR
|
||||
};
|
||||
|
||||
pipeline = PipelineBuilder()
|
||||
.setShaders(vertStage, fragStage)
|
||||
.setVertexInput({binding}, attrs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_POINT_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(pipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
.build(device);
|
||||
|
||||
vertModule.destroy();
|
||||
fragModule.destroy();
|
||||
}
|
||||
|
||||
void MountDust::spawnDust(const glm::vec3& position, const glm::vec3& velocity, bool isMoving) {
|
||||
if (!isMoving) {
|
||||
spawnAccum = 0.0f;
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ bool QuestMarkerRenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFr
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS) // depth test on, write off
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(vkCtx_->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
|
|
@ -179,6 +180,63 @@ void QuestMarkerRenderer::shutdown() {
|
|||
vkCtx_ = nullptr;
|
||||
}
|
||||
|
||||
void QuestMarkerRenderer::recreatePipelines() {
|
||||
if (!vkCtx_) return;
|
||||
VkDevice device = vkCtx_->getDevice();
|
||||
|
||||
// Destroy old pipeline (NOT layout)
|
||||
if (pipeline_ != VK_NULL_HANDLE) {
|
||||
vkDestroyPipeline(device, pipeline_, nullptr);
|
||||
pipeline_ = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VkShaderModule vertModule;
|
||||
vertModule.loadFromFile(device, "assets/shaders/quest_marker.vert.spv");
|
||||
VkShaderModule fragModule;
|
||||
fragModule.loadFromFile(device, "assets/shaders/quest_marker.frag.spv");
|
||||
|
||||
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
|
||||
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
VkVertexInputBindingDescription binding{};
|
||||
binding.binding = 0;
|
||||
binding.stride = 5 * sizeof(float);
|
||||
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
VkVertexInputAttributeDescription posAttr{};
|
||||
posAttr.location = 0;
|
||||
posAttr.binding = 0;
|
||||
posAttr.format = VK_FORMAT_R32G32B32_SFLOAT;
|
||||
posAttr.offset = 0;
|
||||
|
||||
VkVertexInputAttributeDescription uvAttr{};
|
||||
uvAttr.location = 1;
|
||||
uvAttr.binding = 0;
|
||||
uvAttr.format = VK_FORMAT_R32G32_SFLOAT;
|
||||
uvAttr.offset = 3 * sizeof(float);
|
||||
|
||||
std::vector<VkDynamicState> dynamicStates = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR
|
||||
};
|
||||
|
||||
pipeline_ = PipelineBuilder()
|
||||
.setShaders(vertStage, fragStage)
|
||||
.setVertexInput({binding}, {posAttr, uvAttr})
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(vkCtx_->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
.build(device);
|
||||
|
||||
vertModule.destroy();
|
||||
fragModule.destroy();
|
||||
}
|
||||
|
||||
void QuestMarkerRenderer::createDescriptorResources() {
|
||||
VkDevice device = vkCtx_->getDevice();
|
||||
|
||||
|
|
|
|||
|
|
@ -723,6 +723,66 @@ void Renderer::shutdown() {
|
|||
LOG_INFO("Renderer shutdown");
|
||||
}
|
||||
|
||||
void Renderer::setMsaaSamples(VkSampleCountFlagBits samples) {
|
||||
if (!vkCtx) return;
|
||||
|
||||
VkSampleCountFlagBits current = vkCtx->getMsaaSamples();
|
||||
if (samples == current) return;
|
||||
|
||||
LOG_INFO("Changing MSAA from ", static_cast<int>(current), "x to ", static_cast<int>(samples), "x");
|
||||
|
||||
vkDeviceWaitIdle(vkCtx->getDevice());
|
||||
|
||||
// Set new MSAA and recreate swapchain (render pass, depth, MSAA image, framebuffers)
|
||||
vkCtx->setMsaaSamples(samples);
|
||||
vkCtx->recreateSwapchain(window->getWidth(), window->getHeight());
|
||||
|
||||
// Recreate all sub-renderer pipelines (they embed sample count from render pass)
|
||||
if (terrainRenderer) terrainRenderer->recreatePipelines();
|
||||
if (waterRenderer) waterRenderer->recreatePipelines();
|
||||
if (wmoRenderer) wmoRenderer->recreatePipelines();
|
||||
if (m2Renderer) m2Renderer->recreatePipelines();
|
||||
if (characterRenderer) characterRenderer->recreatePipelines();
|
||||
if (questMarkerRenderer) questMarkerRenderer->recreatePipelines();
|
||||
if (weather) weather->recreatePipelines();
|
||||
if (swimEffects) swimEffects->recreatePipelines();
|
||||
if (mountDust) mountDust->recreatePipelines();
|
||||
if (chargeEffect) chargeEffect->recreatePipelines();
|
||||
|
||||
// Sky system sub-renderers
|
||||
if (skySystem) {
|
||||
if (auto* sb = skySystem->getSkybox()) sb->recreatePipelines();
|
||||
if (auto* sf = skySystem->getStarField()) sf->recreatePipelines();
|
||||
if (auto* ce = skySystem->getCelestial()) ce->recreatePipelines();
|
||||
if (auto* cl = skySystem->getClouds()) cl->recreatePipelines();
|
||||
if (auto* lf = skySystem->getLensFlare()) lf->recreatePipelines();
|
||||
}
|
||||
|
||||
// Lightning is standalone (not instantiated in Renderer, no action needed)
|
||||
// Selection circle + overlay use lazy init, just destroy them
|
||||
VkDevice device = vkCtx->getDevice();
|
||||
if (selCirclePipeline) { vkDestroyPipeline(device, selCirclePipeline, nullptr); selCirclePipeline = VK_NULL_HANDLE; }
|
||||
if (overlayPipeline) { vkDestroyPipeline(device, overlayPipeline, nullptr); overlayPipeline = VK_NULL_HANDLE; }
|
||||
|
||||
// Reinitialize ImGui Vulkan backend with new MSAA sample count
|
||||
ImGui_ImplVulkan_Shutdown();
|
||||
ImGui_ImplVulkan_InitInfo initInfo{};
|
||||
initInfo.ApiVersion = VK_API_VERSION_1_1;
|
||||
initInfo.Instance = vkCtx->getInstance();
|
||||
initInfo.PhysicalDevice = vkCtx->getPhysicalDevice();
|
||||
initInfo.Device = vkCtx->getDevice();
|
||||
initInfo.QueueFamily = vkCtx->getGraphicsQueueFamily();
|
||||
initInfo.Queue = vkCtx->getGraphicsQueue();
|
||||
initInfo.DescriptorPool = vkCtx->getImGuiDescriptorPool();
|
||||
initInfo.MinImageCount = 2;
|
||||
initInfo.ImageCount = vkCtx->getSwapchainImageCount();
|
||||
initInfo.PipelineInfoMain.RenderPass = vkCtx->getImGuiRenderPass();
|
||||
initInfo.PipelineInfoMain.MSAASamples = vkCtx->getMsaaSamples();
|
||||
ImGui_ImplVulkan_Init(&initInfo);
|
||||
|
||||
LOG_INFO("MSAA change complete");
|
||||
}
|
||||
|
||||
void Renderer::beginFrame() {
|
||||
if (!vkCtx) return;
|
||||
|
||||
|
|
@ -2784,6 +2844,7 @@ void Renderer::initSelectionCircle() {
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setNoDepthTest()
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(selCirclePipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
|
||||
|
|
@ -2894,6 +2955,7 @@ void Renderer::initOverlayPipeline() {
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setNoDepthTest()
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(overlayPipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ bool Skybox::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) {
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL) // depth test on, write off, LEQUAL for far plane
|
||||
.setColorBlendAttachment(PipelineBuilder::blendDisabled())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(pipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
|
|
@ -84,6 +85,53 @@ bool Skybox::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void Skybox::recreatePipelines() {
|
||||
if (!vkCtx) return;
|
||||
VkDevice device = vkCtx->getDevice();
|
||||
|
||||
if (pipeline != VK_NULL_HANDLE) { vkDestroyPipeline(device, pipeline, nullptr); pipeline = VK_NULL_HANDLE; }
|
||||
|
||||
VkShaderModule vertModule;
|
||||
if (!vertModule.loadFromFile(device, "assets/shaders/skybox.vert.spv")) {
|
||||
LOG_ERROR("Skybox::recreatePipelines: failed to load vertex shader");
|
||||
return;
|
||||
}
|
||||
VkShaderModule fragModule;
|
||||
if (!fragModule.loadFromFile(device, "assets/shaders/skybox.frag.spv")) {
|
||||
LOG_ERROR("Skybox::recreatePipelines: failed to load fragment shader");
|
||||
vertModule.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
|
||||
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
std::vector<VkDynamicState> dynamicStates = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR
|
||||
};
|
||||
|
||||
pipeline = PipelineBuilder()
|
||||
.setShaders(vertStage, fragStage)
|
||||
.setVertexInput({}, {})
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendDisabled())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(pipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
.build(device);
|
||||
|
||||
vertModule.destroy();
|
||||
fragModule.destroy();
|
||||
|
||||
if (pipeline == VK_NULL_HANDLE) {
|
||||
LOG_ERROR("Skybox::recreatePipelines: failed to create pipeline");
|
||||
}
|
||||
}
|
||||
|
||||
void Skybox::shutdown() {
|
||||
if (vkCtx) {
|
||||
VkDevice device = vkCtx->getDevice();
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ bool StarField::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout)
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL) // depth test, no write (stars behind sky)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAdditive())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(pipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.build(device);
|
||||
|
|
@ -108,6 +109,71 @@ bool StarField::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout)
|
|||
return true;
|
||||
}
|
||||
|
||||
void StarField::recreatePipelines() {
|
||||
if (!vkCtx) return;
|
||||
VkDevice device = vkCtx->getDevice();
|
||||
|
||||
if (pipeline != VK_NULL_HANDLE) { vkDestroyPipeline(device, pipeline, nullptr); pipeline = VK_NULL_HANDLE; }
|
||||
|
||||
VkShaderModule vertModule;
|
||||
if (!vertModule.loadFromFile(device, "assets/shaders/starfield.vert.spv")) {
|
||||
LOG_ERROR("StarField::recreatePipelines: failed to load vertex shader");
|
||||
return;
|
||||
}
|
||||
VkShaderModule fragModule;
|
||||
if (!fragModule.loadFromFile(device, "assets/shaders/starfield.frag.spv")) {
|
||||
LOG_ERROR("StarField::recreatePipelines: failed to load fragment shader");
|
||||
vertModule.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
|
||||
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
// Vertex input (same as initialize)
|
||||
VkVertexInputBindingDescription binding{};
|
||||
binding.binding = 0;
|
||||
binding.stride = 5 * sizeof(float);
|
||||
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
VkVertexInputAttributeDescription posAttr{};
|
||||
posAttr.location = 0;
|
||||
posAttr.binding = 0;
|
||||
posAttr.format = VK_FORMAT_R32G32B32_SFLOAT;
|
||||
posAttr.offset = 0;
|
||||
|
||||
VkVertexInputAttributeDescription brightnessAttr{};
|
||||
brightnessAttr.location = 1;
|
||||
brightnessAttr.binding = 0;
|
||||
brightnessAttr.format = VK_FORMAT_R32_SFLOAT;
|
||||
brightnessAttr.offset = 3 * sizeof(float);
|
||||
|
||||
VkVertexInputAttributeDescription twinkleAttr{};
|
||||
twinkleAttr.location = 2;
|
||||
twinkleAttr.binding = 0;
|
||||
twinkleAttr.format = VK_FORMAT_R32_SFLOAT;
|
||||
twinkleAttr.offset = 4 * sizeof(float);
|
||||
|
||||
pipeline = PipelineBuilder()
|
||||
.setShaders(vertStage, fragStage)
|
||||
.setVertexInput({binding}, {posAttr, brightnessAttr, twinkleAttr})
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_POINT_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAdditive())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(pipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.build(device);
|
||||
|
||||
vertModule.destroy();
|
||||
fragModule.destroy();
|
||||
|
||||
if (pipeline == VK_NULL_HANDLE) {
|
||||
LOG_ERROR("StarField::recreatePipelines: failed to create pipeline");
|
||||
}
|
||||
}
|
||||
|
||||
void StarField::shutdown() {
|
||||
destroyStarBuffers();
|
||||
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ bool SwimEffects::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayou
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(ripplePipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
|
|
@ -136,6 +137,7 @@ bool SwimEffects::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayou
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(bubblePipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
|
|
@ -225,6 +227,100 @@ void SwimEffects::shutdown() {
|
|||
bubbles.clear();
|
||||
}
|
||||
|
||||
void SwimEffects::recreatePipelines() {
|
||||
if (!vkCtx) return;
|
||||
VkDevice device = vkCtx->getDevice();
|
||||
|
||||
// Destroy old pipelines (NOT layouts)
|
||||
if (ripplePipeline != VK_NULL_HANDLE) {
|
||||
vkDestroyPipeline(device, ripplePipeline, nullptr);
|
||||
ripplePipeline = VK_NULL_HANDLE;
|
||||
}
|
||||
if (bubblePipeline != VK_NULL_HANDLE) {
|
||||
vkDestroyPipeline(device, bubblePipeline, nullptr);
|
||||
bubblePipeline = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
// Shared vertex input: pos(vec3) + size(float) + alpha(float) = 5 floats
|
||||
VkVertexInputBindingDescription binding{};
|
||||
binding.binding = 0;
|
||||
binding.stride = 5 * sizeof(float);
|
||||
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
std::vector<VkVertexInputAttributeDescription> attrs(3);
|
||||
attrs[0].location = 0;
|
||||
attrs[0].binding = 0;
|
||||
attrs[0].format = VK_FORMAT_R32G32B32_SFLOAT;
|
||||
attrs[0].offset = 0;
|
||||
attrs[1].location = 1;
|
||||
attrs[1].binding = 0;
|
||||
attrs[1].format = VK_FORMAT_R32_SFLOAT;
|
||||
attrs[1].offset = 3 * sizeof(float);
|
||||
attrs[2].location = 2;
|
||||
attrs[2].binding = 0;
|
||||
attrs[2].format = VK_FORMAT_R32_SFLOAT;
|
||||
attrs[2].offset = 4 * sizeof(float);
|
||||
|
||||
std::vector<VkDynamicState> dynamicStates = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR
|
||||
};
|
||||
|
||||
// ---- Rebuild ripple pipeline ----
|
||||
{
|
||||
VkShaderModule vertModule;
|
||||
vertModule.loadFromFile(device, "assets/shaders/swim_ripple.vert.spv");
|
||||
VkShaderModule fragModule;
|
||||
fragModule.loadFromFile(device, "assets/shaders/swim_ripple.frag.spv");
|
||||
|
||||
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
|
||||
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
ripplePipeline = PipelineBuilder()
|
||||
.setShaders(vertStage, fragStage)
|
||||
.setVertexInput({binding}, attrs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_POINT_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(ripplePipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
.build(device);
|
||||
|
||||
vertModule.destroy();
|
||||
fragModule.destroy();
|
||||
}
|
||||
|
||||
// ---- Rebuild bubble pipeline ----
|
||||
{
|
||||
VkShaderModule vertModule;
|
||||
vertModule.loadFromFile(device, "assets/shaders/swim_bubble.vert.spv");
|
||||
VkShaderModule fragModule;
|
||||
fragModule.loadFromFile(device, "assets/shaders/swim_bubble.frag.spv");
|
||||
|
||||
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
|
||||
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
bubblePipeline = PipelineBuilder()
|
||||
.setShaders(vertStage, fragStage)
|
||||
.setVertexInput({binding}, attrs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_POINT_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(bubblePipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
.build(device);
|
||||
|
||||
vertModule.destroy();
|
||||
fragModule.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
void SwimEffects::spawnRipple(const glm::vec3& pos, const glm::vec3& moveDir, float waterH) {
|
||||
if (static_cast<int>(ripples.size()) >= MAX_RIPPLE_PARTICLES) return;
|
||||
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ bool TerrainRenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameL
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, true, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendDisabled())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(pipelineLayout)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
|
||||
|
|
@ -159,6 +160,7 @@ bool TerrainRenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameL
|
|||
.setRasterization(VK_POLYGON_MODE_LINE, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, true, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendDisabled())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(pipelineLayout)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
|
||||
|
|
@ -188,6 +190,86 @@ bool TerrainRenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameL
|
|||
return true;
|
||||
}
|
||||
|
||||
void TerrainRenderer::recreatePipelines() {
|
||||
if (!vkCtx) return;
|
||||
VkDevice device = vkCtx->getDevice();
|
||||
|
||||
// Destroy old pipelines (keep layouts)
|
||||
if (pipeline) { vkDestroyPipeline(device, pipeline, nullptr); pipeline = VK_NULL_HANDLE; }
|
||||
if (wireframePipeline) { vkDestroyPipeline(device, wireframePipeline, nullptr); wireframePipeline = VK_NULL_HANDLE; }
|
||||
|
||||
// Load shaders
|
||||
VkShaderModule vertShader, fragShader;
|
||||
if (!vertShader.loadFromFile(device, "assets/shaders/terrain.vert.spv")) {
|
||||
LOG_ERROR("TerrainRenderer::recreatePipelines: failed to load vertex shader");
|
||||
return;
|
||||
}
|
||||
if (!fragShader.loadFromFile(device, "assets/shaders/terrain.frag.spv")) {
|
||||
LOG_ERROR("TerrainRenderer::recreatePipelines: failed to load fragment shader");
|
||||
vertShader.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
// Vertex input (same as initialize)
|
||||
VkVertexInputBindingDescription vertexBinding{};
|
||||
vertexBinding.binding = 0;
|
||||
vertexBinding.stride = sizeof(pipeline::TerrainVertex);
|
||||
vertexBinding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
std::vector<VkVertexInputAttributeDescription> vertexAttribs(4);
|
||||
vertexAttribs[0] = { 0, 0, VK_FORMAT_R32G32B32_SFLOAT,
|
||||
static_cast<uint32_t>(offsetof(pipeline::TerrainVertex, position)) };
|
||||
vertexAttribs[1] = { 1, 0, VK_FORMAT_R32G32B32_SFLOAT,
|
||||
static_cast<uint32_t>(offsetof(pipeline::TerrainVertex, normal)) };
|
||||
vertexAttribs[2] = { 2, 0, VK_FORMAT_R32G32_SFLOAT,
|
||||
static_cast<uint32_t>(offsetof(pipeline::TerrainVertex, texCoord)) };
|
||||
vertexAttribs[3] = { 3, 0, VK_FORMAT_R32G32_SFLOAT,
|
||||
static_cast<uint32_t>(offsetof(pipeline::TerrainVertex, layerUV)) };
|
||||
|
||||
VkRenderPass mainPass = vkCtx->getImGuiRenderPass();
|
||||
|
||||
// Rebuild fill pipeline
|
||||
pipeline = PipelineBuilder()
|
||||
.setShaders(vertShader.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
|
||||
fragShader.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT))
|
||||
.setVertexInput({ vertexBinding }, vertexAttribs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, true, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendDisabled())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(pipelineLayout)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
|
||||
.build(device);
|
||||
|
||||
if (!pipeline) {
|
||||
LOG_ERROR("TerrainRenderer::recreatePipelines: failed to create fill pipeline");
|
||||
}
|
||||
|
||||
// Rebuild wireframe pipeline
|
||||
wireframePipeline = PipelineBuilder()
|
||||
.setShaders(vertShader.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
|
||||
fragShader.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT))
|
||||
.setVertexInput({ vertexBinding }, vertexAttribs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_LINE, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, true, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendDisabled())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(pipelineLayout)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
|
||||
.build(device);
|
||||
|
||||
if (!wireframePipeline) {
|
||||
LOG_WARNING("TerrainRenderer::recreatePipelines: wireframe pipeline not available");
|
||||
}
|
||||
|
||||
vertShader.destroy();
|
||||
fragShader.destroy();
|
||||
}
|
||||
|
||||
void TerrainRenderer::shutdown() {
|
||||
LOG_INFO("Shutting down terrain renderer");
|
||||
|
||||
|
|
@ -482,7 +564,39 @@ void TerrainRenderer::writeMaterialDescriptors(VkDescriptorSet set, const Terrai
|
|||
}
|
||||
|
||||
void TerrainRenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const Camera& camera) {
|
||||
if (chunks.empty() || !pipeline) return;
|
||||
if (chunks.empty() || !pipeline) {
|
||||
static int emptyLog = 0;
|
||||
if (++emptyLog <= 3)
|
||||
LOG_WARNING("TerrainRenderer::render: chunks=", chunks.size(), " pipeline=", (pipeline != VK_NULL_HANDLE));
|
||||
return;
|
||||
}
|
||||
|
||||
// One-time diagnostic: log chunk nearest to camera
|
||||
static bool loggedDiag = false;
|
||||
if (!loggedDiag && !chunks.empty()) {
|
||||
loggedDiag = true;
|
||||
glm::vec3 cam = camera.getPosition();
|
||||
// Find chunk nearest to camera
|
||||
const TerrainChunkGPU* nearest = nullptr;
|
||||
float nearestDist = 1e30f;
|
||||
for (const auto& ch : chunks) {
|
||||
float dx = ch.boundingSphereCenter.x - cam.x;
|
||||
float dy = ch.boundingSphereCenter.y - cam.y;
|
||||
float dz = ch.boundingSphereCenter.z - cam.z;
|
||||
float d = dx*dx + dy*dy + dz*dz;
|
||||
if (d < nearestDist) { nearestDist = d; nearest = &ch; }
|
||||
}
|
||||
if (nearest) {
|
||||
float d2d = std::sqrt((nearest->boundingSphereCenter.x-cam.x)*(nearest->boundingSphereCenter.x-cam.x) +
|
||||
(nearest->boundingSphereCenter.y-cam.y)*(nearest->boundingSphereCenter.y-cam.y));
|
||||
LOG_INFO("Terrain diag: chunks=", chunks.size(),
|
||||
" cam=(", cam.x, ",", cam.y, ",", cam.z, ")",
|
||||
" nearest_center=(", nearest->boundingSphereCenter.x, ",", nearest->boundingSphereCenter.y, ",", nearest->boundingSphereCenter.z, ")",
|
||||
" dist2d=", d2d, " dist3d=", std::sqrt(nearestDist),
|
||||
" radius=", nearest->boundingSphereRadius,
|
||||
" matSet=", (nearest->materialSet != VK_NULL_HANDLE ? "ok" : "NULL"));
|
||||
}
|
||||
}
|
||||
|
||||
VkPipeline activePipeline = (wireframe && wireframePipeline) ? wireframePipeline : pipeline;
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, activePipeline);
|
||||
|
|
@ -507,6 +621,13 @@ void TerrainRenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, c
|
|||
renderedChunks = 0;
|
||||
culledChunks = 0;
|
||||
|
||||
// Periodic culling summary (every ~5s at 60fps)
|
||||
static int renderCallCount = 0;
|
||||
if (++renderCallCount % 300 == 1) {
|
||||
glm::vec3 cam = camera.getPosition();
|
||||
LOG_INFO("Terrain render call: total=", chunks.size(), " cam=(", cam.x, ",", cam.y, ",", cam.z, ")");
|
||||
}
|
||||
|
||||
for (const auto& chunk : chunks) {
|
||||
if (!chunk.isValid() || !chunk.materialSet) continue;
|
||||
|
||||
|
|
@ -533,6 +654,11 @@ void TerrainRenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, c
|
|||
vkCmdDrawIndexed(cmd, chunk.indexCount, 1, 0, 0, 0);
|
||||
renderedChunks++;
|
||||
}
|
||||
|
||||
// Log culling result periodically
|
||||
if (renderCallCount % 300 == 1) {
|
||||
LOG_INFO("Terrain culling: rendered=", renderedChunks, " culled=", culledChunks);
|
||||
}
|
||||
}
|
||||
|
||||
void TerrainRenderer::renderShadow(VkCommandBuffer /*cmd*/, const glm::vec3& /*shadowCenter*/, float /*halfExtent*/) {
|
||||
|
|
@ -589,6 +715,13 @@ void TerrainRenderer::destroyChunkGPU(TerrainChunkGPU& chunk) {
|
|||
chunk.paramsUBO = VK_NULL_HANDLE;
|
||||
}
|
||||
chunk.materialSet = VK_NULL_HANDLE;
|
||||
|
||||
// Destroy owned alpha textures (VkTexture::~VkTexture is a no-op, must call destroy() explicitly)
|
||||
VkDevice device = vkCtx->getDevice();
|
||||
for (auto& tex : chunk.ownedAlphaTextures) {
|
||||
if (tex) tex->destroy(device, allocator);
|
||||
}
|
||||
chunk.ownedAlphaTextures.clear();
|
||||
}
|
||||
|
||||
int TerrainRenderer::getTriangleCount() const {
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@ bool VkContext::createSwapchain(int width, int height) {
|
|||
vkb::SwapchainBuilder swapchainBuilder{physicalDevice, device, surface};
|
||||
|
||||
auto swapRet = swapchainBuilder
|
||||
.set_desired_format({VK_FORMAT_B8G8R8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR})
|
||||
.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_desired_min_image_count(2)
|
||||
|
|
@ -331,7 +331,7 @@ bool VkContext::createDepthBuffer() {
|
|||
imgInfo.extent = {swapchainExtent.width, swapchainExtent.height, 1};
|
||||
imgInfo.mipLevels = 1;
|
||||
imgInfo.arrayLayers = 1;
|
||||
imgInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
imgInfo.samples = msaaSamples_;
|
||||
imgInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
imgInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
||||
|
||||
|
|
@ -365,87 +365,252 @@ void VkContext::destroyDepthBuffer() {
|
|||
if (depthImage) { vmaDestroyImage(allocator, depthImage, depthAllocation); depthImage = VK_NULL_HANDLE; depthAllocation = VK_NULL_HANDLE; }
|
||||
}
|
||||
|
||||
bool VkContext::createMsaaColorImage() {
|
||||
if (msaaSamples_ == VK_SAMPLE_COUNT_1_BIT) return true; // No MSAA image needed
|
||||
|
||||
VkImageCreateInfo imgInfo{};
|
||||
imgInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
imgInfo.imageType = VK_IMAGE_TYPE_2D;
|
||||
imgInfo.format = swapchainFormat;
|
||||
imgInfo.extent = {swapchainExtent.width, swapchainExtent.height, 1};
|
||||
imgInfo.mipLevels = 1;
|
||||
imgInfo.arrayLayers = 1;
|
||||
imgInfo.samples = msaaSamples_;
|
||||
imgInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
imgInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
|
||||
|
||||
VmaAllocationCreateInfo allocInfo{};
|
||||
allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
|
||||
allocInfo.preferredFlags = VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT;
|
||||
|
||||
if (vmaCreateImage(allocator, &imgInfo, &allocInfo, &msaaColorImage_, &msaaColorAllocation_, nullptr) != VK_SUCCESS) {
|
||||
LOG_ERROR("Failed to create MSAA color image");
|
||||
return false;
|
||||
}
|
||||
|
||||
VkImageViewCreateInfo viewInfo{};
|
||||
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
viewInfo.image = msaaColorImage_;
|
||||
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
viewInfo.format = swapchainFormat;
|
||||
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
viewInfo.subresourceRange.levelCount = 1;
|
||||
viewInfo.subresourceRange.layerCount = 1;
|
||||
|
||||
if (vkCreateImageView(device, &viewInfo, nullptr, &msaaColorView_) != VK_SUCCESS) {
|
||||
LOG_ERROR("Failed to create MSAA color image view");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VkContext::destroyMsaaColorImage() {
|
||||
if (msaaColorView_) { vkDestroyImageView(device, msaaColorView_, nullptr); msaaColorView_ = VK_NULL_HANDLE; }
|
||||
if (msaaColorImage_) { vmaDestroyImage(allocator, msaaColorImage_, msaaColorAllocation_); msaaColorImage_ = VK_NULL_HANDLE; msaaColorAllocation_ = VK_NULL_HANDLE; }
|
||||
}
|
||||
|
||||
VkSampleCountFlagBits VkContext::getMaxUsableSampleCount() const {
|
||||
VkPhysicalDeviceProperties props;
|
||||
vkGetPhysicalDeviceProperties(physicalDevice, &props);
|
||||
VkSampleCountFlags counts = props.limits.framebufferColorSampleCounts
|
||||
& props.limits.framebufferDepthSampleCounts;
|
||||
if (counts & VK_SAMPLE_COUNT_8_BIT) return VK_SAMPLE_COUNT_8_BIT;
|
||||
if (counts & VK_SAMPLE_COUNT_4_BIT) return VK_SAMPLE_COUNT_4_BIT;
|
||||
if (counts & VK_SAMPLE_COUNT_2_BIT) return VK_SAMPLE_COUNT_2_BIT;
|
||||
return VK_SAMPLE_COUNT_1_BIT;
|
||||
}
|
||||
|
||||
void VkContext::setMsaaSamples(VkSampleCountFlagBits samples) {
|
||||
// Clamp to max supported
|
||||
VkSampleCountFlagBits maxSamples = getMaxUsableSampleCount();
|
||||
if (samples > maxSamples) samples = maxSamples;
|
||||
msaaSamples_ = samples;
|
||||
swapchainDirty = true;
|
||||
}
|
||||
|
||||
bool VkContext::createImGuiResources() {
|
||||
// Create depth buffer first
|
||||
if (!createDepthBuffer()) return false;
|
||||
|
||||
// Render pass with color + depth attachments (used by both scene and ImGui)
|
||||
VkAttachmentDescription attachments[2] = {};
|
||||
// Create MSAA color image if needed
|
||||
if (!createMsaaColorImage()) return false;
|
||||
|
||||
// Color attachment (swapchain image)
|
||||
attachments[0].format = swapchainFormat;
|
||||
attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
bool useMsaa = (msaaSamples_ > VK_SAMPLE_COUNT_1_BIT);
|
||||
|
||||
// Depth attachment
|
||||
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].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
if (useMsaa) {
|
||||
// MSAA render pass: 3 attachments (MSAA color, depth, resolve/swapchain)
|
||||
VkAttachmentDescription attachments[3] = {};
|
||||
|
||||
VkAttachmentReference colorRef{};
|
||||
colorRef.attachment = 0;
|
||||
colorRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
// Attachment 0: MSAA color target
|
||||
attachments[0].format = swapchainFormat;
|
||||
attachments[0].samples = msaaSamples_;
|
||||
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
attachments[0].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
|
||||
VkAttachmentReference depthRef{};
|
||||
depthRef.attachment = 1;
|
||||
depthRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
// Attachment 1: Depth (multisampled)
|
||||
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].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
attachments[1].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;
|
||||
// Attachment 2: Resolve target (swapchain image)
|
||||
attachments[2].format = swapchainFormat;
|
||||
attachments[2].samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
attachments[2].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachments[2].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachments[2].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachments[2].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[2].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
attachments[2].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
|
||||
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;
|
||||
VkAttachmentReference colorRef{};
|
||||
colorRef.attachment = 0;
|
||||
colorRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
|
||||
VkRenderPassCreateInfo rpInfo{};
|
||||
rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
||||
rpInfo.attachmentCount = 2;
|
||||
rpInfo.pAttachments = attachments;
|
||||
rpInfo.subpassCount = 1;
|
||||
rpInfo.pSubpasses = &subpass;
|
||||
rpInfo.dependencyCount = 1;
|
||||
rpInfo.pDependencies = &dependency;
|
||||
VkAttachmentReference depthRef{};
|
||||
depthRef.attachment = 1;
|
||||
depthRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
|
||||
if (vkCreateRenderPass(device, &rpInfo, nullptr, &imguiRenderPass) != VK_SUCCESS) {
|
||||
LOG_ERROR("Failed to create render pass");
|
||||
return false;
|
||||
}
|
||||
VkAttachmentReference resolveRef{};
|
||||
resolveRef.attachment = 2;
|
||||
resolveRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
|
||||
// Create framebuffers (color + depth)
|
||||
swapchainFramebuffers.resize(swapchainImageViews.size());
|
||||
for (size_t i = 0; i < swapchainImageViews.size(); i++) {
|
||||
VkImageView fbAttachments[2] = {swapchainImageViews[i], depthImageView};
|
||||
VkSubpassDescription subpass{};
|
||||
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
subpass.colorAttachmentCount = 1;
|
||||
subpass.pColorAttachments = &colorRef;
|
||||
subpass.pDepthStencilAttachment = &depthRef;
|
||||
subpass.pResolveAttachments = &resolveRef;
|
||||
|
||||
VkFramebufferCreateInfo fbInfo{};
|
||||
fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
||||
fbInfo.renderPass = imguiRenderPass;
|
||||
fbInfo.attachmentCount = 2;
|
||||
fbInfo.pAttachments = fbAttachments;
|
||||
fbInfo.width = swapchainExtent.width;
|
||||
fbInfo.height = swapchainExtent.height;
|
||||
fbInfo.layers = 1;
|
||||
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;
|
||||
|
||||
if (vkCreateFramebuffer(device, &fbInfo, nullptr, &swapchainFramebuffers[i]) != VK_SUCCESS) {
|
||||
LOG_ERROR("Failed to create swapchain framebuffer ", i);
|
||||
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]
|
||||
swapchainFramebuffers.resize(swapchainImageViews.size());
|
||||
for (size_t i = 0; i < swapchainImageViews.size(); i++) {
|
||||
VkImageView fbAttachments[3] = {msaaColorView_, depthImageView, swapchainImageViews[i]};
|
||||
|
||||
VkFramebufferCreateInfo fbInfo{};
|
||||
fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
||||
fbInfo.renderPass = imguiRenderPass;
|
||||
fbInfo.attachmentCount = 3;
|
||||
fbInfo.pAttachments = fbAttachments;
|
||||
fbInfo.width = swapchainExtent.width;
|
||||
fbInfo.height = swapchainExtent.height;
|
||||
fbInfo.layers = 1;
|
||||
|
||||
if (vkCreateFramebuffer(device, &fbInfo, nullptr, &swapchainFramebuffers[i]) != VK_SUCCESS) {
|
||||
LOG_ERROR("Failed to create MSAA swapchain framebuffer ", i);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Non-MSAA render pass: 2 attachments (color + depth) — original path
|
||||
VkAttachmentDescription attachments[2] = {};
|
||||
|
||||
// Color attachment (swapchain image)
|
||||
attachments[0].format = swapchainFormat;
|
||||
attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
|
||||
// Depth attachment
|
||||
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].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
|
||||
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;
|
||||
|
||||
VkSubpassDescription subpass{};
|
||||
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
subpass.colorAttachmentCount = 1;
|
||||
subpass.pColorAttachments = &colorRef;
|
||||
subpass.pDepthStencilAttachment = &depthRef;
|
||||
|
||||
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 = 2;
|
||||
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 render pass");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Framebuffers: [swapchainView, depthView]
|
||||
swapchainFramebuffers.resize(swapchainImageViews.size());
|
||||
for (size_t i = 0; i < swapchainImageViews.size(); i++) {
|
||||
VkImageView fbAttachments[2] = {swapchainImageViews[i], depthImageView};
|
||||
|
||||
VkFramebufferCreateInfo fbInfo{};
|
||||
fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
||||
fbInfo.renderPass = imguiRenderPass;
|
||||
fbInfo.attachmentCount = 2;
|
||||
fbInfo.pAttachments = fbAttachments;
|
||||
fbInfo.width = swapchainExtent.width;
|
||||
fbInfo.height = swapchainExtent.height;
|
||||
fbInfo.layers = 1;
|
||||
|
||||
if (vkCreateFramebuffer(device, &fbInfo, nullptr, &swapchainFramebuffers[i]) != VK_SUCCESS) {
|
||||
LOG_ERROR("Failed to create swapchain framebuffer ", i);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create descriptor pool for ImGui
|
||||
|
|
@ -473,6 +638,7 @@ void VkContext::destroyImGuiResources() {
|
|||
vkDestroyDescriptorPool(device, imguiDescriptorPool, nullptr);
|
||||
imguiDescriptorPool = VK_NULL_HANDLE;
|
||||
}
|
||||
destroyMsaaColorImage();
|
||||
destroyDepthBuffer();
|
||||
// Framebuffers are destroyed in destroySwapchain()
|
||||
if (imguiRenderPass) {
|
||||
|
|
@ -500,7 +666,7 @@ bool VkContext::recreateSwapchain(int width, int height) {
|
|||
|
||||
vkb::SwapchainBuilder swapchainBuilder{physicalDevice, device, surface};
|
||||
auto swapRet = swapchainBuilder
|
||||
.set_desired_format({VK_FORMAT_B8G8R8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR})
|
||||
.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_desired_min_image_count(2)
|
||||
|
|
@ -524,28 +690,168 @@ bool VkContext::recreateSwapchain(int width, int height) {
|
|||
swapchainImages = vkbSwap.get_images().value();
|
||||
swapchainImageViews = vkbSwap.get_image_views().value();
|
||||
|
||||
// Recreate depth buffer
|
||||
// Recreate depth buffer + MSAA color image
|
||||
destroyMsaaColorImage();
|
||||
destroyDepthBuffer();
|
||||
|
||||
// Destroy old render pass (needs recreation if MSAA changed)
|
||||
if (imguiRenderPass) {
|
||||
vkDestroyRenderPass(device, imguiRenderPass, nullptr);
|
||||
imguiRenderPass = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
if (!createDepthBuffer()) return false;
|
||||
if (!createMsaaColorImage()) return false;
|
||||
|
||||
// Recreate framebuffers (color + depth)
|
||||
swapchainFramebuffers.resize(swapchainImageViews.size());
|
||||
for (size_t i = 0; i < swapchainImageViews.size(); i++) {
|
||||
VkImageView fbAttachments[2] = {swapchainImageViews[i], depthImageView};
|
||||
bool useMsaa = (msaaSamples_ > VK_SAMPLE_COUNT_1_BIT);
|
||||
|
||||
VkFramebufferCreateInfo fbInfo{};
|
||||
fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
||||
fbInfo.renderPass = imguiRenderPass;
|
||||
fbInfo.attachmentCount = 2;
|
||||
fbInfo.pAttachments = fbAttachments;
|
||||
fbInfo.width = swapchainExtent.width;
|
||||
fbInfo.height = swapchainExtent.height;
|
||||
fbInfo.layers = 1;
|
||||
if (useMsaa) {
|
||||
// MSAA render pass: 3 attachments
|
||||
VkAttachmentDescription attachments[3] = {};
|
||||
attachments[0].format = swapchainFormat;
|
||||
attachments[0].samples = msaaSamples_;
|
||||
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
attachments[0].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
|
||||
if (vkCreateFramebuffer(device, &fbInfo, nullptr, &swapchainFramebuffers[i]) != VK_SUCCESS) {
|
||||
LOG_ERROR("Failed to recreate swapchain framebuffer ", i);
|
||||
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].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
|
||||
attachments[2].format = swapchainFormat;
|
||||
attachments[2].samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
attachments[2].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachments[2].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachments[2].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachments[2].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
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};
|
||||
|
||||
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]};
|
||||
VkFramebufferCreateInfo fbInfo{};
|
||||
fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
||||
fbInfo.renderPass = imguiRenderPass;
|
||||
fbInfo.attachmentCount = 3;
|
||||
fbInfo.pAttachments = fbAttachments;
|
||||
fbInfo.width = swapchainExtent.width;
|
||||
fbInfo.height = swapchainExtent.height;
|
||||
fbInfo.layers = 1;
|
||||
if (vkCreateFramebuffer(device, &fbInfo, nullptr, &swapchainFramebuffers[i]) != VK_SUCCESS) {
|
||||
LOG_ERROR("Failed to recreate MSAA swapchain framebuffer ", i);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Non-MSAA render pass: 2 attachments
|
||||
VkAttachmentDescription attachments[2] = {};
|
||||
attachments[0].format = swapchainFormat;
|
||||
attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
|
||||
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].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
|
||||
VkAttachmentReference colorRef{0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
|
||||
VkAttachmentReference depthRef{1, 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;
|
||||
|
||||
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 = 2;
|
||||
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 render pass");
|
||||
return false;
|
||||
}
|
||||
|
||||
swapchainFramebuffers.resize(swapchainImageViews.size());
|
||||
for (size_t i = 0; i < swapchainImageViews.size(); i++) {
|
||||
VkImageView fbAttachments[2] = {swapchainImageViews[i], depthImageView};
|
||||
VkFramebufferCreateInfo fbInfo{};
|
||||
fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
||||
fbInfo.renderPass = imguiRenderPass;
|
||||
fbInfo.attachmentCount = 2;
|
||||
fbInfo.pAttachments = fbAttachments;
|
||||
fbInfo.width = swapchainExtent.width;
|
||||
fbInfo.height = swapchainExtent.height;
|
||||
fbInfo.layers = 1;
|
||||
if (vkCreateFramebuffer(device, &fbInfo, nullptr, &swapchainFramebuffers[i]) != VK_SUCCESS) {
|
||||
LOG_ERROR("Failed to recreate swapchain framebuffer ", i);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
swapchainDirty = false;
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ bool WaterRenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLay
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL) // depth test yes, write no
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(pipelineLayout)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
|
||||
|
|
@ -142,6 +143,60 @@ bool WaterRenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLay
|
|||
return true;
|
||||
}
|
||||
|
||||
void WaterRenderer::recreatePipelines() {
|
||||
if (!vkCtx) return;
|
||||
VkDevice device = vkCtx->getDevice();
|
||||
|
||||
// Destroy old pipeline (keep layout)
|
||||
if (waterPipeline) { vkDestroyPipeline(device, waterPipeline, nullptr); waterPipeline = VK_NULL_HANDLE; }
|
||||
|
||||
// Load shaders
|
||||
VkShaderModule vertShader, fragShader;
|
||||
if (!vertShader.loadFromFile(device, "assets/shaders/water.vert.spv")) {
|
||||
LOG_ERROR("WaterRenderer::recreatePipelines: failed to load vertex shader");
|
||||
return;
|
||||
}
|
||||
if (!fragShader.loadFromFile(device, "assets/shaders/water.frag.spv")) {
|
||||
LOG_ERROR("WaterRenderer::recreatePipelines: failed to load fragment shader");
|
||||
vertShader.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
// Vertex input (same as initialize)
|
||||
VkVertexInputBindingDescription vertBinding{};
|
||||
vertBinding.binding = 0;
|
||||
vertBinding.stride = 8 * sizeof(float);
|
||||
vertBinding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
std::vector<VkVertexInputAttributeDescription> vertAttribs = {
|
||||
{ 0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0 },
|
||||
{ 1, 0, VK_FORMAT_R32G32_SFLOAT, 6 * sizeof(float) },
|
||||
};
|
||||
|
||||
VkRenderPass mainPass = vkCtx->getImGuiRenderPass();
|
||||
|
||||
waterPipeline = PipelineBuilder()
|
||||
.setShaders(vertShader.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
|
||||
fragShader.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT))
|
||||
.setVertexInput({ vertBinding }, vertAttribs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(pipelineLayout)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
|
||||
.build(device);
|
||||
|
||||
vertShader.destroy();
|
||||
fragShader.destroy();
|
||||
|
||||
if (!waterPipeline) {
|
||||
LOG_ERROR("WaterRenderer::recreatePipelines: failed to create pipeline");
|
||||
}
|
||||
}
|
||||
|
||||
void WaterRenderer::shutdown() {
|
||||
clear();
|
||||
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ bool Weather::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) {
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS) // depth test on, write off (transparent particles)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(pipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
|
|
@ -115,6 +116,65 @@ bool Weather::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void Weather::recreatePipelines() {
|
||||
if (!vkCtx) return;
|
||||
VkDevice device = vkCtx->getDevice();
|
||||
|
||||
if (pipeline != VK_NULL_HANDLE) { vkDestroyPipeline(device, pipeline, nullptr); pipeline = VK_NULL_HANDLE; }
|
||||
|
||||
VkShaderModule vertModule;
|
||||
if (!vertModule.loadFromFile(device, "assets/shaders/weather.vert.spv")) {
|
||||
LOG_ERROR("Weather::recreatePipelines: failed to load vertex shader");
|
||||
return;
|
||||
}
|
||||
VkShaderModule fragModule;
|
||||
if (!fragModule.loadFromFile(device, "assets/shaders/weather.frag.spv")) {
|
||||
LOG_ERROR("Weather::recreatePipelines: failed to load fragment shader");
|
||||
vertModule.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
|
||||
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
// Vertex input (same as initialize)
|
||||
VkVertexInputBindingDescription binding{};
|
||||
binding.binding = 0;
|
||||
binding.stride = 3 * sizeof(float);
|
||||
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
VkVertexInputAttributeDescription posAttr{};
|
||||
posAttr.location = 0;
|
||||
posAttr.binding = 0;
|
||||
posAttr.format = VK_FORMAT_R32G32B32_SFLOAT;
|
||||
posAttr.offset = 0;
|
||||
|
||||
std::vector<VkDynamicState> dynamicStates = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR
|
||||
};
|
||||
|
||||
pipeline = PipelineBuilder()
|
||||
.setShaders(vertStage, fragStage)
|
||||
.setVertexInput({binding}, {posAttr})
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_POINT_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(pipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
.build(device);
|
||||
|
||||
vertModule.destroy();
|
||||
fragModule.destroy();
|
||||
|
||||
if (pipeline == VK_NULL_HANDLE) {
|
||||
LOG_ERROR("Weather::recreatePipelines: failed to create pipeline");
|
||||
}
|
||||
}
|
||||
|
||||
void Weather::update(const Camera& camera, float deltaTime) {
|
||||
if (!enabled || weatherType == Type::NONE) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -154,6 +154,7 @@ bool WMORenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayou
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, true, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendDisabled())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
|
||||
|
|
@ -175,6 +176,7 @@ bool WMORenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayou
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
|
||||
|
|
@ -193,6 +195,7 @@ bool WMORenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayou
|
|||
.setRasterization(VK_POLYGON_MODE_LINE, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, true, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendDisabled())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
|
||||
|
|
@ -2878,5 +2881,96 @@ float WMORenderer::raycastBoundingBoxes(const glm::vec3& origin, const glm::vec3
|
|||
|
||||
// Occlusion queries stubbed out in Vulkan (were disabled by default anyway)
|
||||
|
||||
void WMORenderer::recreatePipelines() {
|
||||
if (!vkCtx_) return;
|
||||
VkDevice device = vkCtx_->getDevice();
|
||||
vkDeviceWaitIdle(device);
|
||||
|
||||
// Destroy old main-pass pipelines (NOT shadow, NOT pipeline layout)
|
||||
if (opaquePipeline_) { vkDestroyPipeline(device, opaquePipeline_, nullptr); opaquePipeline_ = VK_NULL_HANDLE; }
|
||||
if (transparentPipeline_) { vkDestroyPipeline(device, transparentPipeline_, nullptr); transparentPipeline_ = VK_NULL_HANDLE; }
|
||||
if (wireframePipeline_) { vkDestroyPipeline(device, wireframePipeline_, nullptr); wireframePipeline_ = VK_NULL_HANDLE; }
|
||||
|
||||
// --- Load shaders ---
|
||||
VkShaderModule vertShader, fragShader;
|
||||
if (!vertShader.loadFromFile(device, "assets/shaders/wmo.vert.spv") ||
|
||||
!fragShader.loadFromFile(device, "assets/shaders/wmo.frag.spv")) {
|
||||
core::Logger::getInstance().error("WMORenderer::recreatePipelines: failed to load shaders");
|
||||
return;
|
||||
}
|
||||
|
||||
// --- Vertex input ---
|
||||
struct WMOVertexData {
|
||||
glm::vec3 position;
|
||||
glm::vec3 normal;
|
||||
glm::vec2 texCoord;
|
||||
glm::vec4 color;
|
||||
};
|
||||
|
||||
VkVertexInputBindingDescription vertexBinding{};
|
||||
vertexBinding.binding = 0;
|
||||
vertexBinding.stride = sizeof(WMOVertexData);
|
||||
vertexBinding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
std::vector<VkVertexInputAttributeDescription> vertexAttribs(4);
|
||||
vertexAttribs[0] = { 0, 0, VK_FORMAT_R32G32B32_SFLOAT,
|
||||
static_cast<uint32_t>(offsetof(WMOVertexData, position)) };
|
||||
vertexAttribs[1] = { 1, 0, VK_FORMAT_R32G32B32_SFLOAT,
|
||||
static_cast<uint32_t>(offsetof(WMOVertexData, normal)) };
|
||||
vertexAttribs[2] = { 2, 0, VK_FORMAT_R32G32_SFLOAT,
|
||||
static_cast<uint32_t>(offsetof(WMOVertexData, texCoord)) };
|
||||
vertexAttribs[3] = { 3, 0, VK_FORMAT_R32G32B32A32_SFLOAT,
|
||||
static_cast<uint32_t>(offsetof(WMOVertexData, color)) };
|
||||
|
||||
VkRenderPass mainPass = vkCtx_->getImGuiRenderPass();
|
||||
|
||||
opaquePipeline_ = PipelineBuilder()
|
||||
.setShaders(vertShader.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
|
||||
fragShader.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT))
|
||||
.setVertexInput({ vertexBinding }, vertexAttribs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, true, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendDisabled())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
|
||||
.build(device);
|
||||
|
||||
transparentPipeline_ = PipelineBuilder()
|
||||
.setShaders(vertShader.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
|
||||
fragShader.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT))
|
||||
.setVertexInput({ vertexBinding }, vertexAttribs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
|
||||
.build(device);
|
||||
|
||||
wireframePipeline_ = PipelineBuilder()
|
||||
.setShaders(vertShader.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
|
||||
fragShader.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT))
|
||||
.setVertexInput({ vertexBinding }, vertexAttribs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_LINE, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, true, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendDisabled())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
|
||||
.build(device);
|
||||
|
||||
vertShader.destroy();
|
||||
fragShader.destroy();
|
||||
|
||||
core::Logger::getInstance().info("WMORenderer: pipelines recreated");
|
||||
}
|
||||
|
||||
} // namespace rendering
|
||||
} // namespace wowee
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue