Kelsidavis-WoWee/src/rendering/vk_pipeline.cpp
Paul d54e262048 feat(rendering): GPU architecture + visual quality fixes
M2 GPU instancing
- M2InstanceGPU SSBO (96 B/entry, double-buffered, 16384 max)
- Group opaque instances by (modelId, LOD); single vkCmdDrawIndexed per group
- boneBase field indexes into mega bone SSBO via gl_InstanceIndex

Indirect terrain drawing
- 24 MB mega index buffer (6M uint32) + 64 MB mega vertex buffer
- CPU builds VkDrawIndexedIndirectCommand per visible chunk
- Single VB/IB bind per frame; shadow pass reuses mega buffers
- Replaced vkCmdDrawIndexedIndirect with direct vkCmdDrawIndexed to fix
  host-mapped buffer race condition that caused terrain flickering

GPU frustum culling (compute shader)
- m2_cull.comp.glsl: 64-thread workgroups, sphere-vs-6-planes + distance cull
- CullInstanceGPU SSBO input, uint visibility[] output, double-buffered
- dispatchCullCompute() runs before main pass via render graph node

Consolidated bone matrix SSBOs
- 16 MB double-buffered mega bone SSBO (2048 instances × 128 bones)
- Eliminated per-instance descriptor sets; one megaBoneSet_ per frame
- prepareRender() packs bone matrices consecutively into current frame slot

Render graph / frame graph
- RenderGraph: RGResource handles, RGPass nodes, Kahn topological sort
- Automatic VkImageMemoryBarrier/VkBufferMemoryBarrier between passes
- Passes: minimap_composite, worldmap_composite, preview_composite,
  shadow_pass, reflection_pass, compute_cull
- beginFrame() uses buildFrameGraph() + renderGraph_->execute(cmd)

Pipeline derivatives
- PipelineBuilder::setFlags/setBasePipeline for VK_PIPELINE_CREATE_DERIVATIVE_BIT
- M2 opaque = base; alphaTest/alpha/additive are derivatives
- Applied to terrain (wireframe) and WMO (alpha-test) renderers

Rendering bug fixes:
- fix(shadow): compute lightSpaceMatrix before updatePerFrameUBO to eliminate
  one-frame lag that caused shadow trails and flicker on moving objects
- fix(shadow): scale depth bias with shadowDistance_ instead of hardcoded 0.8f
  to prevent acne at close range and gaps at far range
- fix(visibility): WMO group distance threshold 500u → 1200u to match terrain
  view distance; buildings were disappearing on the horizon
- fix(precision): camera near plane 0.05 → 0.5 (ratio 600K:1 → 60K:1),
  eliminating Z-fighting and improving frustum plane extraction stability
- fix(streaming): terrain load radius 4 → 6 tiles (~2133u → ~3200u) to exceed
  M2 render distance (2800u) and eliminate pop-in when camera turns;
  unload radius 7 → 9; spawn radius 3 → 4
- fix(visibility): ground-detail M2 distance multiplier 0.75 → 0.9 to reduce
  early pop of grass and debris
2026-04-04 13:43:16 +03:00

307 lines
11 KiB
C++

#include "rendering/vk_pipeline.hpp"
#include "core/logger.hpp"
namespace wowee {
namespace rendering {
PipelineBuilder::PipelineBuilder() {
// Default: one blend attachment with blending disabled
colorBlendAttachments_.push_back(blendDisabled());
// Default dynamic states: viewport + scissor (almost always dynamic)
dynamicStates_ = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
}
PipelineBuilder& PipelineBuilder::setShaders(
VkPipelineShaderStageCreateInfo vert, VkPipelineShaderStageCreateInfo frag)
{
shaderStages_ = {vert, frag};
return *this;
}
PipelineBuilder& PipelineBuilder::setVertexInput(
const std::vector<VkVertexInputBindingDescription>& bindings,
const std::vector<VkVertexInputAttributeDescription>& attributes)
{
vertexBindings_ = bindings;
vertexAttributes_ = attributes;
return *this;
}
PipelineBuilder& PipelineBuilder::setNoVertexInput() {
vertexBindings_.clear();
vertexAttributes_.clear();
return *this;
}
PipelineBuilder& PipelineBuilder::setTopology(VkPrimitiveTopology topology,
VkBool32 primitiveRestart)
{
topology_ = topology;
primitiveRestart_ = primitiveRestart;
return *this;
}
PipelineBuilder& PipelineBuilder::setRasterization(VkPolygonMode polygonMode,
VkCullModeFlags cullMode, VkFrontFace frontFace)
{
polygonMode_ = polygonMode;
cullMode_ = cullMode;
frontFace_ = frontFace;
return *this;
}
PipelineBuilder& PipelineBuilder::setDepthTest(bool enable, bool writeEnable,
VkCompareOp compareOp)
{
depthTestEnable_ = enable;
depthWriteEnable_ = writeEnable;
depthCompareOp_ = compareOp;
return *this;
}
PipelineBuilder& PipelineBuilder::setNoDepthTest() {
depthTestEnable_ = false;
depthWriteEnable_ = false;
return *this;
}
PipelineBuilder& PipelineBuilder::setDepthBias(float constantFactor, float slopeFactor) {
depthBiasEnable_ = true;
depthBiasConstant_ = constantFactor;
depthBiasSlope_ = slopeFactor;
return *this;
}
PipelineBuilder& PipelineBuilder::setColorBlendAttachment(
VkPipelineColorBlendAttachmentState blendState)
{
colorBlendAttachments_ = {blendState};
return *this;
}
PipelineBuilder& PipelineBuilder::setNoColorAttachment() {
colorBlendAttachments_.clear();
return *this;
}
PipelineBuilder& PipelineBuilder::setMultisample(VkSampleCountFlagBits samples) {
msaaSamples_ = samples;
return *this;
}
PipelineBuilder& PipelineBuilder::setAlphaToCoverage(bool enable) {
alphaToCoverage_ = enable;
return *this;
}
PipelineBuilder& PipelineBuilder::setLayout(VkPipelineLayout layout) {
pipelineLayout_ = layout;
return *this;
}
PipelineBuilder& PipelineBuilder::setRenderPass(VkRenderPass renderPass, uint32_t subpass) {
renderPass_ = renderPass;
subpass_ = subpass;
return *this;
}
PipelineBuilder& PipelineBuilder::setDynamicStates(const std::vector<VkDynamicState>& states) {
dynamicStates_ = states;
return *this;
}
// Pipeline derivatives — hint driver to share compiled state between similar pipelines
PipelineBuilder& PipelineBuilder::setFlags(VkPipelineCreateFlags flags) {
flags_ = flags;
return *this;
}
PipelineBuilder& PipelineBuilder::setBasePipeline(VkPipeline basePipeline) {
basePipelineHandle_ = basePipeline;
return *this;
}
VkPipeline PipelineBuilder::build(VkDevice device, VkPipelineCache cache) const {
// Vertex input
VkPipelineVertexInputStateCreateInfo vertexInput{};
vertexInput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexInput.vertexBindingDescriptionCount = static_cast<uint32_t>(vertexBindings_.size());
vertexInput.pVertexBindingDescriptions = vertexBindings_.data();
vertexInput.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexAttributes_.size());
vertexInput.pVertexAttributeDescriptions = vertexAttributes_.data();
// Input assembly
VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputAssembly.topology = topology_;
inputAssembly.primitiveRestartEnable = primitiveRestart_;
// Viewport / scissor (dynamic, so just specify count)
VkPipelineViewportStateCreateInfo viewportState{};
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewportState.viewportCount = 1;
viewportState.scissorCount = 1;
// Rasterization
VkPipelineRasterizationStateCreateInfo rasterizer{};
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterizer.depthClampEnable = VK_FALSE;
rasterizer.rasterizerDiscardEnable = VK_FALSE;
rasterizer.polygonMode = polygonMode_;
rasterizer.lineWidth = 1.0f;
rasterizer.cullMode = cullMode_;
rasterizer.frontFace = frontFace_;
rasterizer.depthBiasEnable = depthBiasEnable_ ? VK_TRUE : VK_FALSE;
rasterizer.depthBiasConstantFactor = depthBiasConstant_;
rasterizer.depthBiasSlopeFactor = depthBiasSlope_;
// Multisampling
VkPipelineMultisampleStateCreateInfo multisampling{};
multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisampling.sampleShadingEnable = VK_FALSE;
multisampling.rasterizationSamples = msaaSamples_;
multisampling.alphaToCoverageEnable = alphaToCoverage_ ? VK_TRUE : VK_FALSE;
// Depth/stencil
VkPipelineDepthStencilStateCreateInfo depthStencil{};
depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
depthStencil.depthTestEnable = depthTestEnable_ ? VK_TRUE : VK_FALSE;
depthStencil.depthWriteEnable = depthWriteEnable_ ? VK_TRUE : VK_FALSE;
depthStencil.depthCompareOp = depthCompareOp_;
depthStencil.depthBoundsTestEnable = VK_FALSE;
depthStencil.stencilTestEnable = VK_FALSE;
// Color blending
VkPipelineColorBlendStateCreateInfo colorBlending{};
colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
colorBlending.logicOpEnable = VK_FALSE;
colorBlending.attachmentCount = static_cast<uint32_t>(colorBlendAttachments_.size());
colorBlending.pAttachments = colorBlendAttachments_.data();
// Dynamic state
VkPipelineDynamicStateCreateInfo dynamicState{};
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates_.size());
dynamicState.pDynamicStates = dynamicStates_.data();
// Create pipeline
VkGraphicsPipelineCreateInfo pipelineInfo{};
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipelineInfo.stageCount = static_cast<uint32_t>(shaderStages_.size());
pipelineInfo.pStages = shaderStages_.data();
pipelineInfo.pVertexInputState = &vertexInput;
pipelineInfo.pInputAssemblyState = &inputAssembly;
pipelineInfo.pViewportState = &viewportState;
pipelineInfo.pRasterizationState = &rasterizer;
pipelineInfo.pMultisampleState = &multisampling;
pipelineInfo.pDepthStencilState = &depthStencil;
pipelineInfo.pColorBlendState = colorBlendAttachments_.empty() ? nullptr : &colorBlending;
pipelineInfo.pDynamicState = dynamicStates_.empty() ? nullptr : &dynamicState;
pipelineInfo.layout = pipelineLayout_;
pipelineInfo.flags = flags_;
pipelineInfo.basePipelineHandle = basePipelineHandle_;
pipelineInfo.basePipelineIndex = -1;
pipelineInfo.renderPass = renderPass_;
pipelineInfo.subpass = subpass_;
VkPipeline pipeline = VK_NULL_HANDLE;
if (vkCreateGraphicsPipelines(device, cache, 1, &pipelineInfo,
nullptr, &pipeline) != VK_SUCCESS)
{
LOG_ERROR("Failed to create graphics pipeline");
return VK_NULL_HANDLE;
}
return pipeline;
}
// All RGBA channels enabled — used by every blend mode since we never need to
// mask individual channels (WoW's fixed-function pipeline always writes all four).
static constexpr VkColorComponentFlags kColorWriteAll =
VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
VkPipelineColorBlendAttachmentState PipelineBuilder::blendDisabled() {
VkPipelineColorBlendAttachmentState state{};
state.colorWriteMask = kColorWriteAll;
state.blendEnable = VK_FALSE;
return state;
}
VkPipelineColorBlendAttachmentState PipelineBuilder::blendAlpha() {
VkPipelineColorBlendAttachmentState state{};
state.colorWriteMask = kColorWriteAll;
state.blendEnable = VK_TRUE;
state.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
state.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
state.colorBlendOp = VK_BLEND_OP_ADD;
state.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
state.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
state.alphaBlendOp = VK_BLEND_OP_ADD;
return state;
}
VkPipelineColorBlendAttachmentState PipelineBuilder::blendPremultiplied() {
VkPipelineColorBlendAttachmentState state{};
state.colorWriteMask = kColorWriteAll;
state.blendEnable = VK_TRUE;
state.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
state.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
state.colorBlendOp = VK_BLEND_OP_ADD;
state.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
state.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
state.alphaBlendOp = VK_BLEND_OP_ADD;
return state;
}
VkPipelineColorBlendAttachmentState PipelineBuilder::blendAdditive() {
VkPipelineColorBlendAttachmentState state{};
state.colorWriteMask = kColorWriteAll;
state.blendEnable = VK_TRUE;
state.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
state.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
state.colorBlendOp = VK_BLEND_OP_ADD;
state.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
state.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
state.alphaBlendOp = VK_BLEND_OP_ADD;
return state;
}
VkPipelineLayout createPipelineLayout(VkDevice device,
const std::vector<VkDescriptorSetLayout>& setLayouts,
const std::vector<VkPushConstantRange>& pushConstants)
{
VkPipelineLayoutCreateInfo layoutInfo{};
layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
layoutInfo.setLayoutCount = static_cast<uint32_t>(setLayouts.size());
layoutInfo.pSetLayouts = setLayouts.data();
layoutInfo.pushConstantRangeCount = static_cast<uint32_t>(pushConstants.size());
layoutInfo.pPushConstantRanges = pushConstants.data();
VkPipelineLayout layout = VK_NULL_HANDLE;
if (vkCreatePipelineLayout(device, &layoutInfo, nullptr, &layout) != VK_SUCCESS) {
LOG_ERROR("Failed to create pipeline layout");
}
return layout;
}
VkDescriptorSetLayout createDescriptorSetLayout(VkDevice device,
const std::vector<VkDescriptorSetLayoutBinding>& bindings)
{
VkDescriptorSetLayoutCreateInfo layoutInfo{};
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
layoutInfo.bindingCount = static_cast<uint32_t>(bindings.size());
layoutInfo.pBindings = bindings.data();
VkDescriptorSetLayout layout = VK_NULL_HANDLE;
if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &layout) != VK_SUCCESS) {
LOG_ERROR("Failed to create descriptor set layout");
}
return layout;
}
} // namespace rendering
} // namespace wowee