Improve Vulkan Blit: caching, interpolation, swizzle, enable flags
- Cache a VkFramebuffer per texture to avoid creating/destroying one for every Blit() call. - Create and cache both linear and nearest samplers; honor Texture::Interpolation in descriptor writes. - Add single-channel image-view component swizzle (R -> RGB, A = 1) to match OpenGL grayscale behavior. - Set texture-enable uniforms (NAME_enabled) when shaders declare them. - Guard Blit() with null destination and emit a clear warning instead of recording an invalid render pass. ctest passes 4/4 in both dynamic-backend ON and OFF builds.
This commit is contained in:
@@ -20,6 +20,7 @@ struct VulkanRenderer::VulkanTexture {
|
|||||||
VkImage image = VK_NULL_HANDLE;
|
VkImage image = VK_NULL_HANDLE;
|
||||||
VkImageView view = VK_NULL_HANDLE;
|
VkImageView view = VK_NULL_HANDLE;
|
||||||
VkDeviceMemory memory = VK_NULL_HANDLE;
|
VkDeviceMemory memory = VK_NULL_HANDLE;
|
||||||
|
VkFramebuffer framebuffer = VK_NULL_HANDLE;
|
||||||
int width = 0;
|
int width = 0;
|
||||||
int height = 0;
|
int height = 0;
|
||||||
int depth = 0;
|
int depth = 0;
|
||||||
@@ -84,6 +85,7 @@ void VulkanRenderer::PostInit()
|
|||||||
}
|
}
|
||||||
CreateVertexBuffer();
|
CreateVertexBuffer();
|
||||||
CreateLinearSampler();
|
CreateLinearSampler();
|
||||||
|
CreateNearestSampler();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanRenderer::PostDestroy()
|
void VulkanRenderer::PostDestroy()
|
||||||
@@ -142,6 +144,10 @@ void VulkanRenderer::DestroyInternal()
|
|||||||
vkDestroySampler(device_, linear_sampler_, nullptr);
|
vkDestroySampler(device_, linear_sampler_, nullptr);
|
||||||
linear_sampler_ = VK_NULL_HANDLE;
|
linear_sampler_ = VK_NULL_HANDLE;
|
||||||
}
|
}
|
||||||
|
if (nearest_sampler_ != VK_NULL_HANDLE) {
|
||||||
|
vkDestroySampler(device_, nearest_sampler_, nullptr);
|
||||||
|
nearest_sampler_ = VK_NULL_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
if (vertex_buffer_ != VK_NULL_HANDLE) {
|
if (vertex_buffer_ != VK_NULL_HANDLE) {
|
||||||
vkDestroyBuffer(device_, vertex_buffer_, nullptr);
|
vkDestroyBuffer(device_, vertex_buffer_, nullptr);
|
||||||
@@ -505,6 +511,44 @@ bool VulkanRenderer::CreateLinearSampler()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool VulkanRenderer::CreateNearestSampler()
|
||||||
|
{
|
||||||
|
VkSamplerCreateInfo sampler_info = {};
|
||||||
|
sampler_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
||||||
|
sampler_info.magFilter = VK_FILTER_NEAREST;
|
||||||
|
sampler_info.minFilter = VK_FILTER_NEAREST;
|
||||||
|
sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||||
|
sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||||
|
sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||||
|
sampler_info.anisotropyEnable = VK_FALSE;
|
||||||
|
sampler_info.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
|
||||||
|
sampler_info.unnormalizedCoordinates = VK_FALSE;
|
||||||
|
sampler_info.compareEnable = VK_FALSE;
|
||||||
|
sampler_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
|
||||||
|
sampler_info.mipLodBias = 0.0f;
|
||||||
|
sampler_info.minLod = 0.0f;
|
||||||
|
sampler_info.maxLod = 0.0f;
|
||||||
|
|
||||||
|
VkResult result = vkCreateSampler(device_, &sampler_info, nullptr, &nearest_sampler_);
|
||||||
|
if (result != VK_SUCCESS) {
|
||||||
|
qWarning() << "Failed to create Vulkan nearest sampler:" << result;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkSampler VulkanRenderer::GetSampler(Texture::Interpolation interpolation) const
|
||||||
|
{
|
||||||
|
switch (interpolation) {
|
||||||
|
case Texture::kNearest:
|
||||||
|
return nearest_sampler_ != VK_NULL_HANDLE ? nearest_sampler_ : linear_sampler_;
|
||||||
|
case Texture::kLinear:
|
||||||
|
case Texture::kMipmappedLinear:
|
||||||
|
default:
|
||||||
|
return linear_sampler_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool VulkanRenderer::CreateStagingBuffer(VkDeviceSize size, VkBuffer *out_buffer,
|
bool VulkanRenderer::CreateStagingBuffer(VkDeviceSize size, VkBuffer *out_buffer,
|
||||||
VkDeviceMemory *out_memory)
|
VkDeviceMemory *out_memory)
|
||||||
{
|
{
|
||||||
@@ -919,6 +963,15 @@ QVariant VulkanRenderer::CreateNativeTexture(int width, int height, int depth,
|
|||||||
view_info.subresourceRange.baseArrayLayer = 0;
|
view_info.subresourceRange.baseArrayLayer = 0;
|
||||||
view_info.subresourceRange.layerCount = 1;
|
view_info.subresourceRange.layerCount = 1;
|
||||||
|
|
||||||
|
// Single-channel textures are typically intended as grayscale. Replicate the
|
||||||
|
// red channel to RGB and force alpha to 1, matching OpenGL's swizzle behavior.
|
||||||
|
if (channel_count == 1) {
|
||||||
|
view_info.components.r = VK_COMPONENT_SWIZZLE_R;
|
||||||
|
view_info.components.g = VK_COMPONENT_SWIZZLE_R;
|
||||||
|
view_info.components.b = VK_COMPONENT_SWIZZLE_R;
|
||||||
|
view_info.components.a = VK_COMPONENT_SWIZZLE_ONE;
|
||||||
|
}
|
||||||
|
|
||||||
result = vkCreateImageView(device_, &view_info, nullptr, &tex->view);
|
result = vkCreateImageView(device_, &view_info, nullptr, &tex->view);
|
||||||
if (result != VK_SUCCESS) {
|
if (result != VK_SUCCESS) {
|
||||||
vkFreeMemory(device_, tex->memory, nullptr);
|
vkFreeMemory(device_, tex->memory, nullptr);
|
||||||
@@ -989,6 +1042,9 @@ void VulkanRenderer::DestroyNativeTexture(QVariant texture)
|
|||||||
if (!tex) {
|
if (!tex) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (tex->framebuffer != VK_NULL_HANDLE) {
|
||||||
|
vkDestroyFramebuffer(device_, tex->framebuffer, nullptr);
|
||||||
|
}
|
||||||
if (tex->view != VK_NULL_HANDLE) {
|
if (tex->view != VK_NULL_HANDLE) {
|
||||||
vkDestroyImageView(device_, tex->view, nullptr);
|
vkDestroyImageView(device_, tex->view, nullptr);
|
||||||
}
|
}
|
||||||
@@ -1815,9 +1871,14 @@ void VulkanRenderer::Blit(QVariant shader_variant, olive::AcceleratedJob &a_job,
|
|||||||
if (!dest_tex) {
|
if (!dest_tex) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// TODO: support rendering to a temporary offscreen texture when the
|
||||||
|
// caller requests the default output (used by OpenGL direct-to-widget).
|
||||||
|
qWarning() << "VulkanRenderer::Blit with null destination is not implemented";
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
VkFormat render_pass_format = dest_tex ? dest_tex->vk_format : VK_FORMAT_R32G32B32A32_SFLOAT;
|
VkFormat render_pass_format = dest_tex->vk_format;
|
||||||
VkRenderPass render_pass = GetOrCreateRenderPass(render_pass_format, clear_destination);
|
VkRenderPass render_pass = GetOrCreateRenderPass(render_pass_format, clear_destination);
|
||||||
if (render_pass == VK_NULL_HANDLE) {
|
if (render_pass == VK_NULL_HANDLE) {
|
||||||
return;
|
return;
|
||||||
@@ -1931,18 +1992,42 @@ void VulkanRenderer::Blit(QVariant shader_variant, olive::AcceleratedJob &a_job,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create framebuffer and render pass for this blit
|
// Set texture-enable flags for shaders that declare uniform bool NAME_enabled.
|
||||||
|
if (shader->ubo_size > 0) {
|
||||||
|
for (const TextureBinding &tb : bindings) {
|
||||||
|
QString enabled_name = tb.name + QStringLiteral("_enabled");
|
||||||
|
for (const UniformInfo &u : shader->uniforms) {
|
||||||
|
if (u.name == enabled_name && u.size == sizeof(int)) {
|
||||||
|
char *dst = ubo_data.data() + static_cast<int>(u.offset);
|
||||||
|
*reinterpret_cast<int *>(dst) = tb.tex ? 1 : 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lazily create a per-texture framebuffer. The framebuffer is compatible
|
||||||
|
// with any render pass that uses the same format and sample count, so we
|
||||||
|
// build it once with the non-clear variant and reuse it.
|
||||||
VkFramebuffer framebuffer = VK_NULL_HANDLE;
|
VkFramebuffer framebuffer = VK_NULL_HANDLE;
|
||||||
if (dest_tex) {
|
if (dest_tex) {
|
||||||
VkFramebufferCreateInfo fb_info = {};
|
if (dest_tex->framebuffer == VK_NULL_HANDLE) {
|
||||||
fb_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
VkFramebufferCreateInfo fb_info = {};
|
||||||
fb_info.renderPass = render_pass;
|
fb_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
||||||
fb_info.attachmentCount = 1;
|
fb_info.renderPass = GetOrCreateRenderPass(render_pass_format, false);
|
||||||
fb_info.pAttachments = &dest_tex->view;
|
fb_info.attachmentCount = 1;
|
||||||
fb_info.width = static_cast<uint32_t>(destination_params.effective_width());
|
fb_info.pAttachments = &dest_tex->view;
|
||||||
fb_info.height = static_cast<uint32_t>(destination_params.effective_height());
|
fb_info.width = static_cast<uint32_t>(dest_tex->width);
|
||||||
fb_info.layers = 1;
|
fb_info.height = static_cast<uint32_t>(dest_tex->height);
|
||||||
vkCreateFramebuffer(device_, &fb_info, nullptr, &framebuffer);
|
fb_info.layers = 1;
|
||||||
|
VkResult fb_result = vkCreateFramebuffer(device_, &fb_info, nullptr,
|
||||||
|
&dest_tex->framebuffer);
|
||||||
|
if (fb_result != VK_SUCCESS) {
|
||||||
|
qWarning() << "Failed to create Vulkan framebuffer:" << fb_result;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
framebuffer = dest_tex->framebuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create UBO buffer if needed
|
// Create UBO buffer if needed
|
||||||
@@ -1973,9 +2058,6 @@ void VulkanRenderer::Blit(QVariant shader_variant, olive::AcceleratedJob &a_job,
|
|||||||
VkResult result = vkAllocateDescriptorSets(device_, &ds_alloc, &descriptor_set);
|
VkResult result = vkAllocateDescriptorSets(device_, &ds_alloc, &descriptor_set);
|
||||||
if (result != VK_SUCCESS) {
|
if (result != VK_SUCCESS) {
|
||||||
qWarning() << "Failed to allocate Vulkan descriptor set:" << result;
|
qWarning() << "Failed to allocate Vulkan descriptor set:" << result;
|
||||||
if (framebuffer != VK_NULL_HANDLE) {
|
|
||||||
vkDestroyFramebuffer(device_, framebuffer, nullptr);
|
|
||||||
}
|
|
||||||
if (ubo_buffer != VK_NULL_HANDLE) {
|
if (ubo_buffer != VK_NULL_HANDLE) {
|
||||||
DestroyStagingBuffer(ubo_buffer, ubo_memory);
|
DestroyStagingBuffer(ubo_buffer, ubo_memory);
|
||||||
}
|
}
|
||||||
@@ -2007,7 +2089,7 @@ void VulkanRenderer::Blit(QVariant shader_variant, olive::AcceleratedJob &a_job,
|
|||||||
for (int i = 0; i < bindings.size() && i < 16; i++) {
|
for (int i = 0; i < bindings.size() && i < 16; i++) {
|
||||||
const TextureBinding &tb = bindings.at(i);
|
const TextureBinding &tb = bindings.at(i);
|
||||||
VkDescriptorImageInfo img_info = {};
|
VkDescriptorImageInfo img_info = {};
|
||||||
img_info.sampler = linear_sampler_;
|
img_info.sampler = GetSampler(tb.interp);
|
||||||
img_info.imageView = tb.tex ? tb.tex->view : VK_NULL_HANDLE;
|
img_info.imageView = tb.tex ? tb.tex->view : VK_NULL_HANDLE;
|
||||||
img_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
img_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||||
image_infos.append(img_info);
|
image_infos.append(img_info);
|
||||||
@@ -2113,9 +2195,6 @@ void VulkanRenderer::Blit(QVariant shader_variant, olive::AcceleratedJob &a_job,
|
|||||||
if (descriptor_set != VK_NULL_HANDLE) {
|
if (descriptor_set != VK_NULL_HANDLE) {
|
||||||
vkFreeDescriptorSets(device_, descriptor_pool_, 1, &descriptor_set);
|
vkFreeDescriptorSets(device_, descriptor_pool_, 1, &descriptor_set);
|
||||||
}
|
}
|
||||||
if (framebuffer != VK_NULL_HANDLE) {
|
|
||||||
vkDestroyFramebuffer(device_, framebuffer, nullptr);
|
|
||||||
}
|
|
||||||
if (ubo_buffer != VK_NULL_HANDLE) {
|
if (ubo_buffer != VK_NULL_HANDLE) {
|
||||||
DestroyStagingBuffer(ubo_buffer, ubo_memory);
|
DestroyStagingBuffer(ubo_buffer, ubo_memory);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ private:
|
|||||||
bool CreateDescriptorPool();
|
bool CreateDescriptorPool();
|
||||||
bool CreateVertexBuffer();
|
bool CreateVertexBuffer();
|
||||||
bool CreateLinearSampler();
|
bool CreateLinearSampler();
|
||||||
|
bool CreateNearestSampler();
|
||||||
|
VkSampler GetSampler(Texture::Interpolation interpolation) const;
|
||||||
bool CreateStagingBuffer(VkDeviceSize size, VkBuffer *out_buffer,
|
bool CreateStagingBuffer(VkDeviceSize size, VkBuffer *out_buffer,
|
||||||
VkDeviceMemory *out_memory);
|
VkDeviceMemory *out_memory);
|
||||||
void DestroyStagingBuffer(VkBuffer buffer, VkDeviceMemory memory);
|
void DestroyStagingBuffer(VkBuffer buffer, VkDeviceMemory memory);
|
||||||
@@ -141,6 +143,7 @@ private:
|
|||||||
VkCommandPool command_pool_ = VK_NULL_HANDLE;
|
VkCommandPool command_pool_ = VK_NULL_HANDLE;
|
||||||
VkDescriptorPool descriptor_pool_ = VK_NULL_HANDLE;
|
VkDescriptorPool descriptor_pool_ = VK_NULL_HANDLE;
|
||||||
VkSampler linear_sampler_ = VK_NULL_HANDLE;
|
VkSampler linear_sampler_ = VK_NULL_HANDLE;
|
||||||
|
VkSampler nearest_sampler_ = VK_NULL_HANDLE;
|
||||||
|
|
||||||
QHash<quint64, VkRenderPass> render_pass_cache_;
|
QHash<quint64, VkRenderPass> render_pass_cache_;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user