mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 17:43:52 +00:00
Fix character preview facing and add 4x MSAA to preview render target
Character was facing stage-left (yaw 180) instead of toward camera; corrected default yaw to 90. Added MSAA support to VkRenderTarget with automatic resolve attachment, and enabled 4x MSAA for the character preview off-screen pass.
This commit is contained in:
parent
d65b170774
commit
9a1b78bffd
6 changed files with 229 additions and 108 deletions
|
|
@ -89,7 +89,7 @@ private:
|
||||||
bool modelLoaded_ = false;
|
bool modelLoaded_ = false;
|
||||||
bool compositeRequested_ = false;
|
bool compositeRequested_ = false;
|
||||||
bool compositeRendered_ = false; // True after first successful compositePass
|
bool compositeRendered_ = false; // True after first successful compositePass
|
||||||
float modelYaw_ = 180.0f;
|
float modelYaw_ = 90.0f;
|
||||||
|
|
||||||
// Cached info from loadCharacter() for later recompositing.
|
// Cached info from loadCharacter() for later recompositing.
|
||||||
game::Race race_ = game::Race::HUMAN;
|
game::Race race_ = game::Race::HUMAN;
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,8 @@ public:
|
||||||
~CharacterRenderer();
|
~CharacterRenderer();
|
||||||
|
|
||||||
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout, pipeline::AssetManager* am,
|
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout, pipeline::AssetManager* am,
|
||||||
VkRenderPass renderPassOverride = VK_NULL_HANDLE);
|
VkRenderPass renderPassOverride = VK_NULL_HANDLE,
|
||||||
|
VkSampleCountFlagBits msaaSamples = VK_SAMPLE_COUNT_1_BIT);
|
||||||
void shutdown();
|
void shutdown();
|
||||||
|
|
||||||
void setAssetManager(pipeline::AssetManager* am) { assetManager = am; }
|
void setAssetManager(pipeline::AssetManager* am) { assetManager = am; }
|
||||||
|
|
@ -230,6 +231,7 @@ public:
|
||||||
private:
|
private:
|
||||||
VkContext* vkCtx_ = nullptr;
|
VkContext* vkCtx_ = nullptr;
|
||||||
VkRenderPass renderPassOverride_ = VK_NULL_HANDLE;
|
VkRenderPass renderPassOverride_ = VK_NULL_HANDLE;
|
||||||
|
VkSampleCountFlagBits msaaSamplesOverride_ = VK_SAMPLE_COUNT_1_BIT;
|
||||||
pipeline::AssetManager* assetManager = nullptr;
|
pipeline::AssetManager* assetManager = nullptr;
|
||||||
|
|
||||||
// Vulkan pipelines (one per blend mode)
|
// Vulkan pipelines (one per blend mode)
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ class VkContext;
|
||||||
/**
|
/**
|
||||||
* Off-screen render target encapsulating VkRenderPass + VkFramebuffer + color VkImage.
|
* Off-screen render target encapsulating VkRenderPass + VkFramebuffer + color VkImage.
|
||||||
* Used for minimap compositing, world map compositing, and other off-screen passes.
|
* Used for minimap compositing, world map compositing, and other off-screen passes.
|
||||||
|
* Supports optional MSAA with automatic resolve.
|
||||||
*/
|
*/
|
||||||
class VkRenderTarget {
|
class VkRenderTarget {
|
||||||
public:
|
public:
|
||||||
|
|
@ -26,9 +27,11 @@ public:
|
||||||
* Create the render target with given dimensions and format.
|
* Create the render target with given dimensions and format.
|
||||||
* Creates: color image, image view, sampler, render pass, framebuffer.
|
* Creates: color image, image view, sampler, render pass, framebuffer.
|
||||||
* When withDepth is true, also creates a D32_SFLOAT depth attachment.
|
* When withDepth is true, also creates a D32_SFLOAT depth attachment.
|
||||||
|
* When msaaSamples > 1, creates multisampled images and a resolve attachment.
|
||||||
*/
|
*/
|
||||||
bool create(VkContext& ctx, uint32_t width, uint32_t height,
|
bool create(VkContext& ctx, uint32_t width, uint32_t height,
|
||||||
VkFormat format = VK_FORMAT_R8G8B8A8_UNORM, bool withDepth = false);
|
VkFormat format = VK_FORMAT_R8G8B8A8_UNORM, bool withDepth = false,
|
||||||
|
VkSampleCountFlagBits msaaSamples = VK_SAMPLE_COUNT_1_BIT);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destroy all Vulkan resources.
|
* Destroy all Vulkan resources.
|
||||||
|
|
@ -48,14 +51,15 @@ public:
|
||||||
*/
|
*/
|
||||||
void endPass(VkCommandBuffer cmd);
|
void endPass(VkCommandBuffer cmd);
|
||||||
|
|
||||||
// Accessors
|
// Accessors - always return the resolved (single-sample) image for reading
|
||||||
VkImage getColorImage() const { return colorImage_.image; }
|
VkImage getColorImage() const { return resolveImage_.image ? resolveImage_.image : colorImage_.image; }
|
||||||
VkImageView getColorImageView() const { return colorImage_.imageView; }
|
VkImageView getColorImageView() const { return resolveImage_.imageView ? resolveImage_.imageView : colorImage_.imageView; }
|
||||||
VkSampler getSampler() const { return sampler_; }
|
VkSampler getSampler() const { return sampler_; }
|
||||||
VkRenderPass getRenderPass() const { return renderPass_; }
|
VkRenderPass getRenderPass() const { return renderPass_; }
|
||||||
VkExtent2D getExtent() const { return { colorImage_.extent.width, colorImage_.extent.height }; }
|
VkExtent2D getExtent() const { return { colorImage_.extent.width, colorImage_.extent.height }; }
|
||||||
VkFormat getFormat() const { return colorImage_.format; }
|
VkFormat getFormat() const { return colorImage_.format; }
|
||||||
bool isValid() const { return framebuffer_ != VK_NULL_HANDLE; }
|
bool isValid() const { return framebuffer_ != VK_NULL_HANDLE; }
|
||||||
|
VkSampleCountFlagBits getSampleCount() const { return msaaSamples_; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Descriptor info for binding the color attachment as a texture in a shader.
|
* Descriptor info for binding the color attachment as a texture in a shader.
|
||||||
|
|
@ -63,9 +67,11 @@ public:
|
||||||
VkDescriptorImageInfo descriptorInfo() const;
|
VkDescriptorImageInfo descriptorInfo() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AllocatedImage colorImage_{};
|
AllocatedImage colorImage_{}; // MSAA color (or single-sample if no MSAA)
|
||||||
|
AllocatedImage resolveImage_{}; // Single-sample resolve target (only when MSAA)
|
||||||
AllocatedImage depthImage_{};
|
AllocatedImage depthImage_{};
|
||||||
bool hasDepth_ = false;
|
bool hasDepth_ = false;
|
||||||
|
VkSampleCountFlagBits msaaSamples_ = VK_SAMPLE_COUNT_1_BIT;
|
||||||
VkSampler sampler_ = VK_NULL_HANDLE;
|
VkSampler sampler_ = VK_NULL_HANDLE;
|
||||||
VkRenderPass renderPass_ = VK_NULL_HANDLE;
|
VkRenderPass renderPass_ = VK_NULL_HANDLE;
|
||||||
VkFramebuffer framebuffer_ = VK_NULL_HANDLE;
|
VkFramebuffer framebuffer_ = VK_NULL_HANDLE;
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,8 @@ bool CharacterPreview::initialize(pipeline::AssetManager* am) {
|
||||||
|
|
||||||
// Initialize CharacterRenderer with our off-screen render pass
|
// Initialize CharacterRenderer with our off-screen render pass
|
||||||
charRenderer_ = std::make_unique<CharacterRenderer>();
|
charRenderer_ = std::make_unique<CharacterRenderer>();
|
||||||
if (!charRenderer_->initialize(vkCtx_, perFrameLayout, am, renderTarget_->getRenderPass())) {
|
if (!charRenderer_->initialize(vkCtx_, perFrameLayout, am, renderTarget_->getRenderPass(),
|
||||||
|
renderTarget_->getSampleCount())) {
|
||||||
LOG_ERROR("CharacterPreview: failed to initialize CharacterRenderer");
|
LOG_ERROR("CharacterPreview: failed to initialize CharacterRenderer");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -103,7 +104,8 @@ void CharacterPreview::createFBO() {
|
||||||
|
|
||||||
// 1. Create off-screen render target with depth
|
// 1. Create off-screen render target with depth
|
||||||
renderTarget_ = std::make_unique<VkRenderTarget>();
|
renderTarget_ = std::make_unique<VkRenderTarget>();
|
||||||
if (!renderTarget_->create(*vkCtx_, fboWidth_, fboHeight_, VK_FORMAT_R8G8B8A8_UNORM, true)) {
|
if (!renderTarget_->create(*vkCtx_, fboWidth_, fboHeight_, VK_FORMAT_R8G8B8A8_UNORM, true,
|
||||||
|
VK_SAMPLE_COUNT_4_BIT)) {
|
||||||
LOG_ERROR("CharacterPreview: failed to create render target");
|
LOG_ERROR("CharacterPreview: failed to create render target");
|
||||||
renderTarget_.reset();
|
renderTarget_.reset();
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -113,13 +113,15 @@ CharacterRenderer::~CharacterRenderer() {
|
||||||
|
|
||||||
bool CharacterRenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout,
|
bool CharacterRenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout,
|
||||||
pipeline::AssetManager* am,
|
pipeline::AssetManager* am,
|
||||||
VkRenderPass renderPassOverride) {
|
VkRenderPass renderPassOverride,
|
||||||
|
VkSampleCountFlagBits msaaSamples) {
|
||||||
core::Logger::getInstance().info("Initializing character renderer (Vulkan)...");
|
core::Logger::getInstance().info("Initializing character renderer (Vulkan)...");
|
||||||
|
|
||||||
vkCtx_ = ctx;
|
vkCtx_ = ctx;
|
||||||
assetManager = am;
|
assetManager = am;
|
||||||
perFrameLayout_ = perFrameLayout;
|
perFrameLayout_ = perFrameLayout;
|
||||||
renderPassOverride_ = renderPassOverride;
|
renderPassOverride_ = renderPassOverride;
|
||||||
|
msaaSamplesOverride_ = msaaSamples;
|
||||||
const unsigned hc = std::thread::hardware_concurrency();
|
const unsigned hc = std::thread::hardware_concurrency();
|
||||||
const size_t availableCores = (hc > 1u) ? static_cast<size_t>(hc - 1u) : 1ull;
|
const size_t availableCores = (hc > 1u) ? static_cast<size_t>(hc - 1u) : 1ull;
|
||||||
// Character updates run alongside M2/WMO work; default to a smaller share.
|
// Character updates run alongside M2/WMO work; default to a smaller share.
|
||||||
|
|
@ -224,7 +226,7 @@ bool CharacterRenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFram
|
||||||
}
|
}
|
||||||
|
|
||||||
VkRenderPass mainPass = renderPassOverride_ ? renderPassOverride_ : vkCtx_->getImGuiRenderPass();
|
VkRenderPass mainPass = renderPassOverride_ ? renderPassOverride_ : vkCtx_->getImGuiRenderPass();
|
||||||
VkSampleCountFlagBits samples = renderPassOverride_ ? VK_SAMPLE_COUNT_1_BIT : vkCtx_->getMsaaSamples();
|
VkSampleCountFlagBits samples = renderPassOverride_ ? msaaSamplesOverride_ : vkCtx_->getMsaaSamples();
|
||||||
|
|
||||||
// --- Vertex input ---
|
// --- Vertex input ---
|
||||||
// CharVertexGPU: vec3 pos(12) + uint8[4] boneWeights(4) + uint8[4] boneIndices(4) +
|
// CharVertexGPU: vec3 pos(12) + uint8[4] boneWeights(4) + uint8[4] boneIndices(4) +
|
||||||
|
|
@ -2946,7 +2948,7 @@ void CharacterRenderer::recreatePipelines() {
|
||||||
}
|
}
|
||||||
|
|
||||||
VkRenderPass mainPass = renderPassOverride_ ? renderPassOverride_ : vkCtx_->getImGuiRenderPass();
|
VkRenderPass mainPass = renderPassOverride_ ? renderPassOverride_ : vkCtx_->getImGuiRenderPass();
|
||||||
VkSampleCountFlagBits samples = renderPassOverride_ ? VK_SAMPLE_COUNT_1_BIT : vkCtx_->getMsaaSamples();
|
VkSampleCountFlagBits samples = renderPassOverride_ ? msaaSamplesOverride_ : vkCtx_->getMsaaSamples();
|
||||||
|
|
||||||
// --- Vertex input ---
|
// --- Vertex input ---
|
||||||
VkVertexInputBindingDescription charBinding{};
|
VkVertexInputBindingDescription charBinding{};
|
||||||
|
|
|
||||||
|
|
@ -9,24 +9,39 @@ VkRenderTarget::~VkRenderTarget() {
|
||||||
// Must call destroy() explicitly with device/allocator before destruction
|
// Must call destroy() explicitly with device/allocator before destruction
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VkRenderTarget::create(VkContext& ctx, uint32_t width, uint32_t height, VkFormat format, bool withDepth) {
|
bool VkRenderTarget::create(VkContext& ctx, uint32_t width, uint32_t height,
|
||||||
|
VkFormat format, bool withDepth, VkSampleCountFlagBits msaaSamples) {
|
||||||
VkDevice device = ctx.getDevice();
|
VkDevice device = ctx.getDevice();
|
||||||
VmaAllocator allocator = ctx.getAllocator();
|
VmaAllocator allocator = ctx.getAllocator();
|
||||||
hasDepth_ = withDepth;
|
hasDepth_ = withDepth;
|
||||||
|
msaaSamples_ = msaaSamples;
|
||||||
|
bool useMSAA = msaaSamples != VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
|
||||||
// Create color image (COLOR_ATTACHMENT + SAMPLED for reading in subsequent passes)
|
// Create color image (multisampled if MSAA)
|
||||||
colorImage_ = createImage(device, allocator, width, height, format,
|
colorImage_ = createImage(device, allocator, width, height, format,
|
||||||
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT);
|
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | (useMSAA ? VkImageUsageFlags(0) : VK_IMAGE_USAGE_SAMPLED_BIT),
|
||||||
|
msaaSamples);
|
||||||
|
|
||||||
if (!colorImage_.image) {
|
if (!colorImage_.image) {
|
||||||
LOG_ERROR("VkRenderTarget: failed to create color image (", width, "x", height, ")");
|
LOG_ERROR("VkRenderTarget: failed to create color image (", width, "x", height, ")");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create depth image if requested
|
// Create resolve image for MSAA (single-sample, sampled for reading)
|
||||||
|
if (useMSAA) {
|
||||||
|
resolveImage_ = createImage(device, allocator, width, height, format,
|
||||||
|
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT);
|
||||||
|
if (!resolveImage_.image) {
|
||||||
|
LOG_ERROR("VkRenderTarget: failed to create resolve image (", width, "x", height, ")");
|
||||||
|
destroy(device, allocator);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create depth image if requested (multisampled to match color)
|
||||||
if (withDepth) {
|
if (withDepth) {
|
||||||
depthImage_ = createImage(device, allocator, width, height,
|
depthImage_ = createImage(device, allocator, width, height,
|
||||||
VK_FORMAT_D32_SFLOAT, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT);
|
VK_FORMAT_D32_SFLOAT, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, msaaSamples);
|
||||||
if (!depthImage_.image) {
|
if (!depthImage_.image) {
|
||||||
LOG_ERROR("VkRenderTarget: failed to create depth image (", width, "x", height, ")");
|
LOG_ERROR("VkRenderTarget: failed to create depth image (", width, "x", height, ")");
|
||||||
destroy(device, allocator);
|
destroy(device, allocator);
|
||||||
|
|
@ -53,102 +68,185 @@ bool VkRenderTarget::create(VkContext& ctx, uint32_t width, uint32_t height, VkF
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create render pass
|
// Create render pass
|
||||||
VkAttachmentDescription attachments[2]{};
|
if (useMSAA) {
|
||||||
// Color attachment: UNDEFINED -> COLOR_ATTACHMENT_OPTIMAL -> SHADER_READ_ONLY_OPTIMAL
|
// MSAA render pass: color(MSAA) + resolve(1x) + optional depth(MSAA)
|
||||||
attachments[0].format = format;
|
// Attachment 0: MSAA color (rendered into, not stored after resolve)
|
||||||
attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
|
// Attachment 1: resolve color (stores final resolved result)
|
||||||
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
// Attachment 2: MSAA depth (optional)
|
||||||
attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
VkAttachmentDescription attachments[3]{};
|
||||||
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_SHADER_READ_ONLY_OPTIMAL;
|
|
||||||
|
|
||||||
// Depth attachment (only used when withDepth)
|
// MSAA color
|
||||||
attachments[1].format = VK_FORMAT_D32_SFLOAT;
|
attachments[0].format = format;
|
||||||
attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
|
attachments[0].samples = msaaSamples;
|
||||||
attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||||
attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; // resolved, don't need MSAA data
|
||||||
attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||||
attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||||
attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
attachments[0].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||||
|
|
||||||
VkAttachmentReference colorRef{};
|
// Resolve color
|
||||||
colorRef.attachment = 0;
|
attachments[1].format = format;
|
||||||
colorRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||||
|
attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||||
|
attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||||
|
attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||||
|
attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
|
attachments[1].finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||||
|
|
||||||
VkAttachmentReference depthRef{};
|
// MSAA depth
|
||||||
depthRef.attachment = 1;
|
attachments[2].format = VK_FORMAT_D32_SFLOAT;
|
||||||
depthRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
attachments[2].samples = msaaSamples;
|
||||||
|
attachments[2].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||||
|
attachments[2].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||||
|
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_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||||
|
|
||||||
VkSubpassDescription subpass{};
|
VkAttachmentReference colorRef{0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
|
||||||
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
VkAttachmentReference resolveRef{1, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
|
||||||
subpass.colorAttachmentCount = 1;
|
VkAttachmentReference depthRef{2, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL};
|
||||||
subpass.pColorAttachments = &colorRef;
|
|
||||||
if (withDepth) subpass.pDepthStencilAttachment = &depthRef;
|
|
||||||
|
|
||||||
// Dependencies
|
VkSubpassDescription subpass{};
|
||||||
VkSubpassDependency dependencies[2]{};
|
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||||
uint32_t depCount = 1;
|
subpass.colorAttachmentCount = 1;
|
||||||
|
subpass.pColorAttachments = &colorRef;
|
||||||
|
subpass.pResolveAttachments = &resolveRef;
|
||||||
|
if (withDepth) subpass.pDepthStencilAttachment = &depthRef;
|
||||||
|
|
||||||
// Input dependency: wait for previous fragment shader reads before writing
|
VkSubpassDependency dep{};
|
||||||
dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
|
dep.srcSubpass = VK_SUBPASS_EXTERNAL;
|
||||||
dependencies[0].dstSubpass = 0;
|
dep.dstSubpass = 0;
|
||||||
dependencies[0].srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
dep.srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||||||
dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
dep.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||||
dependencies[0].srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
dep.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||||
dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
dep.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||||
|
if (withDepth) {
|
||||||
|
dep.dstStageMask |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
|
||||||
|
dep.dstAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
if (withDepth) {
|
VkRenderPassCreateInfo rpInfo{};
|
||||||
dependencies[0].dstStageMask |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
|
rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
||||||
dependencies[0].dstAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
rpInfo.attachmentCount = withDepth ? 3u : 2u;
|
||||||
|
rpInfo.pAttachments = attachments;
|
||||||
|
rpInfo.subpassCount = 1;
|
||||||
|
rpInfo.pSubpasses = &subpass;
|
||||||
|
rpInfo.dependencyCount = 1;
|
||||||
|
rpInfo.pDependencies = &dep;
|
||||||
|
|
||||||
// Output dependency (depth targets only): ensure writes complete before fragment reads
|
if (vkCreateRenderPass(device, &rpInfo, nullptr, &renderPass_) != VK_SUCCESS) {
|
||||||
dependencies[1].srcSubpass = 0;
|
LOG_ERROR("VkRenderTarget: failed to create MSAA render pass");
|
||||||
dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
|
destroy(device, allocator);
|
||||||
dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
|
return false;
|
||||||
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
}
|
||||||
dependencies[1].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
|
||||||
dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
|
// Create framebuffer: MSAA color + resolve + optional MSAA depth
|
||||||
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
VkImageView fbAttachments[3] = { colorImage_.imageView, resolveImage_.imageView, depthImage_.imageView };
|
||||||
dependencies[1].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
VkFramebufferCreateInfo fbInfo{};
|
||||||
depCount = 2;
|
fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
||||||
|
fbInfo.renderPass = renderPass_;
|
||||||
|
fbInfo.attachmentCount = withDepth ? 3u : 2u;
|
||||||
|
fbInfo.pAttachments = fbAttachments;
|
||||||
|
fbInfo.width = width;
|
||||||
|
fbInfo.height = height;
|
||||||
|
fbInfo.layers = 1;
|
||||||
|
|
||||||
|
if (vkCreateFramebuffer(device, &fbInfo, nullptr, &framebuffer_) != VK_SUCCESS) {
|
||||||
|
LOG_ERROR("VkRenderTarget: failed to create MSAA framebuffer");
|
||||||
|
destroy(device, allocator);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Non-MSAA render pass (original path)
|
||||||
|
VkAttachmentDescription attachments[2]{};
|
||||||
|
attachments[0].format = format;
|
||||||
|
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_SHADER_READ_ONLY_OPTIMAL;
|
||||||
|
|
||||||
|
attachments[1].format = VK_FORMAT_D32_SFLOAT;
|
||||||
|
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;
|
||||||
|
if (withDepth) subpass.pDepthStencilAttachment = &depthRef;
|
||||||
|
|
||||||
|
VkSubpassDependency dependencies[2]{};
|
||||||
|
uint32_t depCount = 1;
|
||||||
|
dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
|
||||||
|
dependencies[0].dstSubpass = 0;
|
||||||
|
dependencies[0].srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||||||
|
dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||||
|
dependencies[0].srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||||
|
dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||||
|
|
||||||
|
if (withDepth) {
|
||||||
|
dependencies[0].dstStageMask |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
|
||||||
|
dependencies[0].dstAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||||
|
dependencies[1].srcSubpass = 0;
|
||||||
|
dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
|
||||||
|
dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
|
||||||
|
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
||||||
|
dependencies[1].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||||||
|
dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
|
||||||
|
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||||
|
dependencies[1].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||||
|
depCount = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkRenderPassCreateInfo rpInfo{};
|
||||||
|
rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
||||||
|
rpInfo.attachmentCount = withDepth ? 2u : 1u;
|
||||||
|
rpInfo.pAttachments = attachments;
|
||||||
|
rpInfo.subpassCount = 1;
|
||||||
|
rpInfo.pSubpasses = &subpass;
|
||||||
|
rpInfo.dependencyCount = depCount;
|
||||||
|
rpInfo.pDependencies = dependencies;
|
||||||
|
|
||||||
|
if (vkCreateRenderPass(device, &rpInfo, nullptr, &renderPass_) != VK_SUCCESS) {
|
||||||
|
LOG_ERROR("VkRenderTarget: failed to create render pass");
|
||||||
|
destroy(device, allocator);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkImageView fbAttachments[2] = { colorImage_.imageView, depthImage_.imageView };
|
||||||
|
VkFramebufferCreateInfo fbInfo{};
|
||||||
|
fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
||||||
|
fbInfo.renderPass = renderPass_;
|
||||||
|
fbInfo.attachmentCount = withDepth ? 2u : 1u;
|
||||||
|
fbInfo.pAttachments = fbAttachments;
|
||||||
|
fbInfo.width = width;
|
||||||
|
fbInfo.height = height;
|
||||||
|
fbInfo.layers = 1;
|
||||||
|
|
||||||
|
if (vkCreateFramebuffer(device, &fbInfo, nullptr, &framebuffer_) != VK_SUCCESS) {
|
||||||
|
LOG_ERROR("VkRenderTarget: failed to create framebuffer");
|
||||||
|
destroy(device, allocator);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VkRenderPassCreateInfo rpInfo{};
|
LOG_INFO("VkRenderTarget created (", width, "x", height,
|
||||||
rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
withDepth ? ", depth" : "",
|
||||||
rpInfo.attachmentCount = withDepth ? 2u : 1u;
|
useMSAA ? ", MSAAx" : "", useMSAA ? std::to_string(msaaSamples) : "", ")");
|
||||||
rpInfo.pAttachments = attachments;
|
|
||||||
rpInfo.subpassCount = 1;
|
|
||||||
rpInfo.pSubpasses = &subpass;
|
|
||||||
rpInfo.dependencyCount = depCount;
|
|
||||||
rpInfo.pDependencies = dependencies;
|
|
||||||
|
|
||||||
if (vkCreateRenderPass(device, &rpInfo, nullptr, &renderPass_) != VK_SUCCESS) {
|
|
||||||
LOG_ERROR("VkRenderTarget: failed to create render pass");
|
|
||||||
destroy(device, allocator);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create framebuffer
|
|
||||||
VkImageView fbAttachments[2] = { colorImage_.imageView, depthImage_.imageView };
|
|
||||||
VkFramebufferCreateInfo fbInfo{};
|
|
||||||
fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
|
||||||
fbInfo.renderPass = renderPass_;
|
|
||||||
fbInfo.attachmentCount = withDepth ? 2u : 1u;
|
|
||||||
fbInfo.pAttachments = fbAttachments;
|
|
||||||
fbInfo.width = width;
|
|
||||||
fbInfo.height = height;
|
|
||||||
fbInfo.layers = 1;
|
|
||||||
|
|
||||||
if (vkCreateFramebuffer(device, &fbInfo, nullptr, &framebuffer_) != VK_SUCCESS) {
|
|
||||||
LOG_ERROR("VkRenderTarget: failed to create framebuffer");
|
|
||||||
destroy(device, allocator);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_INFO("VkRenderTarget created (", width, "x", height, withDepth ? ", with depth)" : ")");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,9 +263,11 @@ void VkRenderTarget::destroy(VkDevice device, VmaAllocator allocator) {
|
||||||
vkDestroySampler(device, sampler_, nullptr);
|
vkDestroySampler(device, sampler_, nullptr);
|
||||||
sampler_ = VK_NULL_HANDLE;
|
sampler_ = VK_NULL_HANDLE;
|
||||||
}
|
}
|
||||||
|
destroyImage(device, allocator, resolveImage_);
|
||||||
destroyImage(device, allocator, depthImage_);
|
destroyImage(device, allocator, depthImage_);
|
||||||
destroyImage(device, allocator, colorImage_);
|
destroyImage(device, allocator, colorImage_);
|
||||||
hasDepth_ = false;
|
hasDepth_ = false;
|
||||||
|
msaaSamples_ = VK_SAMPLE_COUNT_1_BIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VkRenderTarget::beginPass(VkCommandBuffer cmd, const VkClearColorValue& clear) {
|
void VkRenderTarget::beginPass(VkCommandBuffer cmd, const VkClearColorValue& clear) {
|
||||||
|
|
@ -178,10 +278,18 @@ void VkRenderTarget::beginPass(VkCommandBuffer cmd, const VkClearColorValue& cle
|
||||||
rpBegin.renderArea.offset = {0, 0};
|
rpBegin.renderArea.offset = {0, 0};
|
||||||
rpBegin.renderArea.extent = getExtent();
|
rpBegin.renderArea.extent = getExtent();
|
||||||
|
|
||||||
VkClearValue clearValues[2]{};
|
VkClearValue clearValues[3]{};
|
||||||
clearValues[0].color = clear;
|
clearValues[0].color = clear; // MSAA color (or single-sample color)
|
||||||
clearValues[1].depthStencil = {1.0f, 0};
|
clearValues[1].color = clear; // resolve (only used for MSAA)
|
||||||
rpBegin.clearValueCount = hasDepth_ ? 2u : 1u;
|
clearValues[2].depthStencil = {1.0f, 0}; // depth
|
||||||
|
|
||||||
|
bool useMSAA = msaaSamples_ != VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
if (useMSAA) {
|
||||||
|
rpBegin.clearValueCount = hasDepth_ ? 3u : 2u;
|
||||||
|
} else {
|
||||||
|
clearValues[1].depthStencil = {1.0f, 0}; // depth is attachment 1 in non-MSAA
|
||||||
|
rpBegin.clearValueCount = hasDepth_ ? 2u : 1u;
|
||||||
|
}
|
||||||
rpBegin.pClearValues = clearValues;
|
rpBegin.pClearValues = clearValues;
|
||||||
|
|
||||||
vkCmdBeginRenderPass(cmd, &rpBegin, VK_SUBPASS_CONTENTS_INLINE);
|
vkCmdBeginRenderPass(cmd, &rpBegin, VK_SUBPASS_CONTENTS_INLINE);
|
||||||
|
|
@ -210,7 +318,8 @@ void VkRenderTarget::endPass(VkCommandBuffer cmd) {
|
||||||
VkDescriptorImageInfo VkRenderTarget::descriptorInfo() const {
|
VkDescriptorImageInfo VkRenderTarget::descriptorInfo() const {
|
||||||
VkDescriptorImageInfo info{};
|
VkDescriptorImageInfo info{};
|
||||||
info.sampler = sampler_;
|
info.sampler = sampler_;
|
||||||
info.imageView = colorImage_.imageView;
|
// Always return the resolved (single-sample) image for shader reads
|
||||||
|
info.imageView = resolveImage_.imageView ? resolveImage_.imageView : colorImage_.imageView;
|
||||||
info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue