mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-02-09 03:40:05 +00:00
This happened when the source texture was an EFB copy, therefore it had not been populated prior to the draw command buffer being executed, and the conversion was occurring in the init command list.
321 lines
12 KiB
C++
321 lines
12 KiB
C++
// Copyright 2016 Dolphin Emulator Project
|
|
// Licensed under GPLv2+
|
|
// Refer to the license.txt file included.
|
|
|
|
#include "VideoBackends/Vulkan/PaletteTextureConverter.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstddef>
|
|
#include <cstring>
|
|
#include <string>
|
|
|
|
#include "Common/Assert.h"
|
|
#include "Common/CommonFuncs.h"
|
|
#include "Common/Logging/Log.h"
|
|
#include "Common/MsgHandler.h"
|
|
|
|
#include "VideoBackends/Vulkan/CommandBufferManager.h"
|
|
#include "VideoBackends/Vulkan/FramebufferManager.h"
|
|
#include "VideoBackends/Vulkan/ObjectCache.h"
|
|
#include "VideoBackends/Vulkan/Renderer.h"
|
|
#include "VideoBackends/Vulkan/StateTracker.h"
|
|
#include "VideoBackends/Vulkan/StreamBuffer.h"
|
|
#include "VideoBackends/Vulkan/Texture2D.h"
|
|
#include "VideoBackends/Vulkan/Util.h"
|
|
#include "VideoBackends/Vulkan/VulkanContext.h"
|
|
|
|
namespace Vulkan
|
|
{
|
|
PaletteTextureConverter::PaletteTextureConverter()
|
|
{
|
|
}
|
|
|
|
PaletteTextureConverter::~PaletteTextureConverter()
|
|
{
|
|
for (const auto& it : m_shaders)
|
|
{
|
|
if (it != VK_NULL_HANDLE)
|
|
vkDestroyShaderModule(g_vulkan_context->GetDevice(), it, nullptr);
|
|
}
|
|
|
|
if (m_palette_buffer_view != VK_NULL_HANDLE)
|
|
vkDestroyBufferView(g_vulkan_context->GetDevice(), m_palette_buffer_view, nullptr);
|
|
|
|
if (m_pipeline_layout != VK_NULL_HANDLE)
|
|
vkDestroyPipelineLayout(g_vulkan_context->GetDevice(), m_pipeline_layout, nullptr);
|
|
|
|
if (m_palette_set_layout != VK_NULL_HANDLE)
|
|
vkDestroyDescriptorSetLayout(g_vulkan_context->GetDevice(), m_palette_set_layout, nullptr);
|
|
}
|
|
|
|
bool PaletteTextureConverter::Initialize()
|
|
{
|
|
if (!CreateBuffers())
|
|
return false;
|
|
|
|
if (!CompileShaders())
|
|
return false;
|
|
|
|
if (!CreateDescriptorLayout())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void PaletteTextureConverter::ConvertTexture(StateTracker* state_tracker,
|
|
VkCommandBuffer command_buffer,
|
|
VkRenderPass render_pass,
|
|
VkFramebuffer dst_framebuffer, Texture2D* src_texture,
|
|
u32 width, u32 height, void* palette,
|
|
TlutFormat format)
|
|
{
|
|
struct PSUniformBlock
|
|
{
|
|
float multiplier;
|
|
int texel_buffer_offset;
|
|
int pad[2];
|
|
};
|
|
|
|
_assert_(static_cast<size_t>(format) < NUM_PALETTE_CONVERSION_SHADERS);
|
|
|
|
size_t palette_size = ((format & 0xF) == GX_TF_I4) ? 32 : 512;
|
|
VkDescriptorSet texel_buffer_descriptor_set;
|
|
|
|
// Allocate memory for the palette, and descriptor sets for the buffer.
|
|
// If any of these fail, execute a command buffer, and try again.
|
|
if (!m_palette_stream_buffer->ReserveMemory(palette_size,
|
|
g_vulkan_context->GetTexelBufferAlignment()) ||
|
|
(texel_buffer_descriptor_set =
|
|
g_command_buffer_mgr->AllocateDescriptorSet(m_palette_set_layout)) == VK_NULL_HANDLE)
|
|
{
|
|
WARN_LOG(VIDEO, "Executing command list while waiting for space in palette buffer");
|
|
Util::ExecuteCurrentCommandsAndRestoreState(state_tracker, false);
|
|
|
|
if (!m_palette_stream_buffer->ReserveMemory(palette_size,
|
|
g_vulkan_context->GetTexelBufferAlignment()) ||
|
|
(texel_buffer_descriptor_set =
|
|
g_command_buffer_mgr->AllocateDescriptorSet(m_palette_set_layout)) == VK_NULL_HANDLE)
|
|
{
|
|
PanicAlert("Failed to allocate space for texture conversion");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Fill descriptor set #2 (texel buffer)
|
|
u32 palette_offset = static_cast<u32>(m_palette_stream_buffer->GetCurrentOffset());
|
|
VkWriteDescriptorSet texel_set_write = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
|
nullptr,
|
|
texel_buffer_descriptor_set,
|
|
0,
|
|
0,
|
|
1,
|
|
VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER,
|
|
nullptr,
|
|
nullptr,
|
|
&m_palette_buffer_view};
|
|
vkUpdateDescriptorSets(g_vulkan_context->GetDevice(), 1, &texel_set_write, 0, nullptr);
|
|
|
|
Util::BufferMemoryBarrier(command_buffer, m_palette_stream_buffer->GetBuffer(),
|
|
VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, palette_offset,
|
|
palette_size, VK_PIPELINE_STAGE_HOST_BIT,
|
|
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
|
|
|
|
// Set up draw
|
|
UtilityShaderDraw draw(command_buffer, m_pipeline_layout, render_pass,
|
|
g_object_cache->GetScreenQuadVertexShader(), VK_NULL_HANDLE,
|
|
m_shaders[format]);
|
|
|
|
VkRect2D region = {{0, 0}, {width, height}};
|
|
draw.BeginRenderPass(dst_framebuffer, region);
|
|
|
|
// Copy in palette
|
|
memcpy(m_palette_stream_buffer->GetCurrentHostPointer(), palette, palette_size);
|
|
m_palette_stream_buffer->CommitMemory(palette_size);
|
|
|
|
// PS Uniforms/Samplers
|
|
PSUniformBlock uniforms = {};
|
|
uniforms.multiplier = ((format & 0xF)) == GX_TF_I4 ? 15.0f : 255.0f;
|
|
uniforms.texel_buffer_offset = static_cast<int>(palette_offset / sizeof(u16));
|
|
draw.SetPushConstants(&uniforms, sizeof(uniforms));
|
|
draw.SetPSSampler(0, src_texture->GetView(), g_object_cache->GetPointSampler());
|
|
|
|
// We have to bind the texel buffer descriptor set separately.
|
|
vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline_layout, 0, 1,
|
|
&texel_buffer_descriptor_set, 0, nullptr);
|
|
|
|
// Draw
|
|
draw.SetViewportAndScissor(0, 0, width, height);
|
|
draw.DrawWithoutVertexBuffer(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, 4);
|
|
draw.EndRenderPass();
|
|
}
|
|
|
|
bool PaletteTextureConverter::CreateBuffers()
|
|
{
|
|
// TODO: Check against maximum size
|
|
static const size_t BUFFER_SIZE = 1024 * 1024;
|
|
|
|
m_palette_stream_buffer =
|
|
StreamBuffer::Create(VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT, BUFFER_SIZE, BUFFER_SIZE);
|
|
if (!m_palette_stream_buffer)
|
|
return false;
|
|
|
|
// Create a view of the whole buffer, we'll offset our texel load into it
|
|
VkBufferViewCreateInfo view_info = {
|
|
VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO, // VkStructureType sType
|
|
nullptr, // const void* pNext
|
|
0, // VkBufferViewCreateFlags flags
|
|
m_palette_stream_buffer->GetBuffer(), // VkBuffer buffer
|
|
VK_FORMAT_R16_UINT, // VkFormat format
|
|
0, // VkDeviceSize offset
|
|
BUFFER_SIZE // VkDeviceSize range
|
|
};
|
|
|
|
VkResult res = vkCreateBufferView(g_vulkan_context->GetDevice(), &view_info, nullptr,
|
|
&m_palette_buffer_view);
|
|
if (res != VK_SUCCESS)
|
|
{
|
|
LOG_VULKAN_ERROR(res, "vkCreateBufferView failed: ");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool PaletteTextureConverter::CompileShaders()
|
|
{
|
|
static const char PALETTE_CONVERSION_FRAGMENT_SHADER_SOURCE[] = R"(
|
|
layout(std140, push_constant) uniform PCBlock
|
|
{
|
|
float multiplier;
|
|
int texture_buffer_offset;
|
|
} PC;
|
|
|
|
layout(set = 1, binding = 0) uniform sampler2DArray samp0;
|
|
layout(set = 0, binding = 0) uniform usamplerBuffer samp1;
|
|
|
|
layout(location = 0) in vec3 f_uv0;
|
|
layout(location = 0) out vec4 ocol0;
|
|
|
|
int Convert3To8(int v)
|
|
{
|
|
// Swizzle bits: 00000123 -> 12312312
|
|
return (v << 5) | (v << 2) | (v >> 1);
|
|
}
|
|
int Convert4To8(int v)
|
|
{
|
|
// Swizzle bits: 00001234 -> 12341234
|
|
return (v << 4) | v;
|
|
}
|
|
int Convert5To8(int v)
|
|
{
|
|
// Swizzle bits: 00012345 -> 12345123
|
|
return (v << 3) | (v >> 2);
|
|
}
|
|
int Convert6To8(int v)
|
|
{
|
|
// Swizzle bits: 00123456 -> 12345612
|
|
return (v << 2) | (v >> 4);
|
|
}
|
|
float4 DecodePixel_RGB5A3(int val)
|
|
{
|
|
int r,g,b,a;
|
|
if ((val&0x8000) > 0)
|
|
{
|
|
r=Convert5To8((val>>10) & 0x1f);
|
|
g=Convert5To8((val>>5 ) & 0x1f);
|
|
b=Convert5To8((val ) & 0x1f);
|
|
a=0xFF;
|
|
}
|
|
else
|
|
{
|
|
a=Convert3To8((val>>12) & 0x7);
|
|
r=Convert4To8((val>>8 ) & 0xf);
|
|
g=Convert4To8((val>>4 ) & 0xf);
|
|
b=Convert4To8((val ) & 0xf);
|
|
}
|
|
return float4(r, g, b, a) / 255.0;
|
|
}
|
|
float4 DecodePixel_RGB565(int val)
|
|
{
|
|
int r, g, b, a;
|
|
r = Convert5To8((val >> 11) & 0x1f);
|
|
g = Convert6To8((val >> 5) & 0x3f);
|
|
b = Convert5To8((val) & 0x1f);
|
|
a = 0xFF;
|
|
return float4(r, g, b, a) / 255.0;
|
|
}
|
|
float4 DecodePixel_IA8(int val)
|
|
{
|
|
int i = val & 0xFF;
|
|
int a = val >> 8;
|
|
return float4(i, i, i, a) / 255.0;
|
|
}
|
|
void main()
|
|
{
|
|
int src = int(round(texture(samp0, f_uv0).r * PC.multiplier));
|
|
src = int(texelFetch(samp1, src + PC.texture_buffer_offset).r);
|
|
src = ((src << 8) & 0xFF00) | (src >> 8);
|
|
ocol0 = DECODE(src);
|
|
}
|
|
|
|
)";
|
|
|
|
std::string palette_ia8_program = StringFromFormat("%s\n%s", "#define DECODE DecodePixel_IA8",
|
|
PALETTE_CONVERSION_FRAGMENT_SHADER_SOURCE);
|
|
std::string palette_rgb565_program = StringFromFormat(
|
|
"%s\n%s", "#define DECODE DecodePixel_RGB565", PALETTE_CONVERSION_FRAGMENT_SHADER_SOURCE);
|
|
std::string palette_rgb5a3_program = StringFromFormat(
|
|
"%s\n%s", "#define DECODE DecodePixel_RGB5A3", PALETTE_CONVERSION_FRAGMENT_SHADER_SOURCE);
|
|
|
|
m_shaders[GX_TL_IA8] = Util::CompileAndCreateFragmentShader(palette_ia8_program);
|
|
m_shaders[GX_TL_RGB565] = Util::CompileAndCreateFragmentShader(palette_rgb565_program);
|
|
m_shaders[GX_TL_RGB5A3] = Util::CompileAndCreateFragmentShader(palette_rgb5a3_program);
|
|
|
|
return (m_shaders[GX_TL_IA8] != VK_NULL_HANDLE && m_shaders[GX_TL_RGB565] != VK_NULL_HANDLE &&
|
|
m_shaders[GX_TL_RGB5A3] != VK_NULL_HANDLE);
|
|
}
|
|
|
|
bool PaletteTextureConverter::CreateDescriptorLayout()
|
|
{
|
|
static const VkDescriptorSetLayoutBinding set_bindings[] = {
|
|
{0, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT},
|
|
};
|
|
static const VkDescriptorSetLayoutCreateInfo set_info = {
|
|
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, nullptr, 0,
|
|
static_cast<u32>(ArraySize(set_bindings)), set_bindings};
|
|
|
|
VkResult res = vkCreateDescriptorSetLayout(g_vulkan_context->GetDevice(), &set_info, nullptr,
|
|
&m_palette_set_layout);
|
|
if (res != VK_SUCCESS)
|
|
{
|
|
LOG_VULKAN_ERROR(res, "vkCreateDescriptorSetLayout failed: ");
|
|
return false;
|
|
}
|
|
|
|
VkDescriptorSetLayout sets[] = {m_palette_set_layout, g_object_cache->GetDescriptorSetLayout(
|
|
DESCRIPTOR_SET_PIXEL_SHADER_SAMPLERS)};
|
|
|
|
VkPushConstantRange push_constant_range = {
|
|
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, PUSH_CONSTANT_BUFFER_SIZE};
|
|
|
|
VkPipelineLayoutCreateInfo pipeline_layout_info = {VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
|
|
nullptr,
|
|
0,
|
|
static_cast<u32>(ArraySize(sets)),
|
|
sets,
|
|
1,
|
|
&push_constant_range};
|
|
|
|
res = vkCreatePipelineLayout(g_vulkan_context->GetDevice(), &pipeline_layout_info, nullptr,
|
|
&m_pipeline_layout);
|
|
if (res != VK_SUCCESS)
|
|
{
|
|
LOG_VULKAN_ERROR(res, "vkCreatePipelineLayout failed: ");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace Vulkan
|