renderer_vulkan: Emulate 3-component vertex formats when unsupported
* This fixes the crashes on AMD
This commit is contained in:
@@ -7,7 +7,7 @@ buildscript {
|
|||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:7.2.0'
|
classpath 'com.android.tools.build:gradle:7.3.1'
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
|
@@ -181,7 +181,8 @@ static std::array<float, 3 * 2> MakeOrthographicMatrix(float width, float height
|
|||||||
}
|
}
|
||||||
|
|
||||||
RendererVulkan::RendererVulkan(Frontend::EmuWindow& window)
|
RendererVulkan::RendererVulkan(Frontend::EmuWindow& window)
|
||||||
: RendererBase{window}, instance{window, Settings::values.physical_device}, scheduler{instance, *this},
|
: RendererBase{window}, instance{window, Settings::values.physical_device},
|
||||||
|
scheduler{instance, renderpass_cache, *this},
|
||||||
renderpass_cache{instance, scheduler}, desc_manager{instance, scheduler},
|
renderpass_cache{instance, scheduler}, desc_manager{instance, scheduler},
|
||||||
runtime{instance, scheduler, renderpass_cache, desc_manager},
|
runtime{instance, scheduler, renderpass_cache, desc_manager},
|
||||||
swapchain{instance, scheduler, renderpass_cache},
|
swapchain{instance, scheduler, renderpass_cache},
|
||||||
@@ -919,7 +920,6 @@ void RendererVulkan::SwapBuffers() {
|
|||||||
PrepareRendertarget();
|
PrepareRendertarget();
|
||||||
|
|
||||||
const auto RecreateSwapchain = [&] {
|
const auto RecreateSwapchain = [&] {
|
||||||
renderpass_cache.ExitRenderpass();
|
|
||||||
scheduler.Finish();
|
scheduler.Finish();
|
||||||
const Layout::FramebufferLayout layout = render_window.GetFramebufferLayout();
|
const Layout::FramebufferLayout layout = render_window.GetFramebufferLayout();
|
||||||
swapchain.Create(layout.width, layout.height);
|
swapchain.Create(layout.width, layout.height);
|
||||||
|
@@ -17,21 +17,21 @@
|
|||||||
|
|
||||||
namespace Vulkan {
|
namespace Vulkan {
|
||||||
|
|
||||||
u32 AttribBytes(VertexAttribute attrib) {
|
u32 AttribBytes(Pica::PipelineRegs::VertexAttributeFormat format, u32 size) {
|
||||||
switch (attrib.type) {
|
switch (format) {
|
||||||
case Pica::PipelineRegs::VertexAttributeFormat::FLOAT:
|
case Pica::PipelineRegs::VertexAttributeFormat::FLOAT:
|
||||||
return sizeof(float) * attrib.size;
|
return sizeof(float) * size;
|
||||||
case Pica::PipelineRegs::VertexAttributeFormat::SHORT:
|
case Pica::PipelineRegs::VertexAttributeFormat::SHORT:
|
||||||
return sizeof(u16) * attrib.size;
|
return sizeof(u16) * size;
|
||||||
case Pica::PipelineRegs::VertexAttributeFormat::BYTE:
|
case Pica::PipelineRegs::VertexAttributeFormat::BYTE:
|
||||||
case Pica::PipelineRegs::VertexAttributeFormat::UBYTE:
|
case Pica::PipelineRegs::VertexAttributeFormat::UBYTE:
|
||||||
return sizeof(u8) * attrib.size;
|
return sizeof(u8) * size;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
vk::Format ToVkAttributeFormat(VertexAttribute attrib) {
|
vk::Format ToVkAttributeFormat(Pica::PipelineRegs::VertexAttributeFormat format, u32 size) {
|
||||||
constexpr std::array attribute_formats = {
|
constexpr std::array attribute_formats = {
|
||||||
std::array{vk::Format::eR8Sint, vk::Format::eR8G8Sint, vk::Format::eR8G8B8Sint,
|
std::array{vk::Format::eR8Sint, vk::Format::eR8G8Sint, vk::Format::eR8G8B8Sint,
|
||||||
vk::Format::eR8G8B8A8Sint},
|
vk::Format::eR8G8B8A8Sint},
|
||||||
@@ -42,8 +42,8 @@ vk::Format ToVkAttributeFormat(VertexAttribute attrib) {
|
|||||||
std::array{vk::Format::eR32Sfloat, vk::Format::eR32G32Sfloat, vk::Format::eR32G32B32Sfloat,
|
std::array{vk::Format::eR32Sfloat, vk::Format::eR32G32Sfloat, vk::Format::eR32G32B32Sfloat,
|
||||||
vk::Format::eR32G32B32A32Sfloat}};
|
vk::Format::eR32G32B32A32Sfloat}};
|
||||||
|
|
||||||
ASSERT(attrib.size <= 4);
|
ASSERT(size <= 4);
|
||||||
return attribute_formats[static_cast<u32>(attrib.type.Value())][attrib.size.Value() - 1];
|
return attribute_formats[static_cast<u32>(format)][size - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
vk::ShaderStageFlagBits ToVkShaderStage(std::size_t index) {
|
vk::ShaderStageFlagBits ToVkShaderStage(std::size_t index) {
|
||||||
@@ -62,6 +62,13 @@ vk::ShaderStageFlagBits ToVkShaderStage(std::size_t index) {
|
|||||||
return vk::ShaderStageFlagBits::eVertex;
|
return vk::ShaderStageFlagBits::eVertex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsAttribFormatSupported(const VertexAttribute& attrib, const Instance& instance) {
|
||||||
|
vk::PhysicalDevice physical_device = instance.GetPhysicalDevice();
|
||||||
|
const vk::Format format = ToVkAttributeFormat(attrib.type, attrib.size);
|
||||||
|
const vk::FormatFeatureFlags features = physical_device.getFormatProperties(format).bufferFeatures;
|
||||||
|
return (features & vk::FormatFeatureFlagBits::eVertexBuffer) == vk::FormatFeatureFlagBits::eVertexBuffer;
|
||||||
|
};
|
||||||
|
|
||||||
PipelineCache::PipelineCache(const Instance& instance, Scheduler& scheduler,
|
PipelineCache::PipelineCache(const Instance& instance, Scheduler& scheduler,
|
||||||
RenderpassCache& renderpass_cache, DescriptorManager& desc_manager)
|
RenderpassCache& renderpass_cache, DescriptorManager& desc_manager)
|
||||||
: instance{instance}, scheduler{scheduler}, renderpass_cache{renderpass_cache}, desc_manager{desc_manager} {
|
: instance{instance}, scheduler{scheduler}, renderpass_cache{renderpass_cache}, desc_manager{desc_manager} {
|
||||||
@@ -179,21 +186,26 @@ void PipelineCache::BindPipeline(const PipelineInfo& info) {
|
|||||||
desc_manager.BindDescriptorSets();
|
desc_manager.BindDescriptorSets();
|
||||||
}
|
}
|
||||||
|
|
||||||
MICROPROFILE_DEFINE(Vulkan_VS, "Vulkan", "Vertex Shader Setup", MP_RGB(192, 128, 128));
|
|
||||||
bool PipelineCache::UseProgrammableVertexShader(const Pica::Regs& regs,
|
bool PipelineCache::UseProgrammableVertexShader(const Pica::Regs& regs,
|
||||||
Pica::Shader::ShaderSetup& setup,
|
Pica::Shader::ShaderSetup& setup,
|
||||||
const VertexLayout& layout) {
|
const VertexLayout& layout) {
|
||||||
MICROPROFILE_SCOPE(Vulkan_VS);
|
|
||||||
|
|
||||||
PicaVSConfig config{regs.vs, setup};
|
PicaVSConfig config{regs.vs, setup};
|
||||||
|
|
||||||
|
u32 emulated_attrib_loc = MAX_VERTEX_ATTRIBUTES;
|
||||||
for (u32 i = 0; i < layout.attribute_count; i++) {
|
for (u32 i = 0; i < layout.attribute_count; i++) {
|
||||||
const auto& attrib = layout.attributes[i];
|
const auto& attrib = layout.attributes[i];
|
||||||
config.state.attrib_types[attrib.location.Value()] = attrib.type.Value();
|
const u32 location = attrib.location.Value();
|
||||||
|
const bool is_supported = IsAttribFormatSupported(attrib, instance);
|
||||||
|
ASSERT(is_supported || attrib.size == 3);
|
||||||
|
|
||||||
|
config.state.attrib_types[location] = attrib.type.Value();
|
||||||
|
config.state.emulated_attrib_locations[location] =
|
||||||
|
is_supported ? 0 : emulated_attrib_loc++;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto [handle, result] =
|
auto [handle, result] =
|
||||||
programmable_vertex_shaders.Get(config, setup, vk::ShaderStageFlagBits::eVertex,
|
programmable_vertex_shaders.Get(config, setup, vk::ShaderStageFlagBits::eVertex,
|
||||||
instance.GetDevice(), ShaderOptimization::Debug);
|
instance.GetDevice(), ShaderOptimization::High);
|
||||||
if (!handle) {
|
if (!handle) {
|
||||||
LOG_ERROR(Render_Vulkan, "Failed to retrieve programmable vertex shader");
|
LOG_ERROR(Render_Vulkan, "Failed to retrieve programmable vertex shader");
|
||||||
return false;
|
return false;
|
||||||
@@ -410,20 +422,36 @@ vk::Pipeline PipelineCache::BuildPipeline(const PipelineInfo& info) {
|
|||||||
: vk::VertexInputRate::eVertex};
|
: vk::VertexInputRate::eVertex};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate vertex attribute structures
|
u32 emulated_attrib_count = 0;
|
||||||
std::array<vk::VertexInputAttributeDescription, MAX_VERTEX_ATTRIBUTES> attributes;
|
std::array<vk::VertexInputAttributeDescription, MAX_VERTEX_ATTRIBUTES * 2> attributes;
|
||||||
for (u32 i = 0; i < info.vertex_layout.attribute_count; i++) {
|
for (u32 i = 0; i < info.vertex_layout.attribute_count; i++) {
|
||||||
const auto& attr = info.vertex_layout.attributes[i];
|
const VertexAttribute& attrib = info.vertex_layout.attributes[i];
|
||||||
attributes[i] = vk::VertexInputAttributeDescription{.location = attr.location,
|
const vk::Format format = ToVkAttributeFormat(attrib.type, attrib.size);
|
||||||
.binding = attr.binding,
|
const bool is_supported = IsAttribFormatSupported(attrib, instance);
|
||||||
.format = ToVkAttributeFormat(attr),
|
ASSERT_MSG(is_supported || attrib.size == 3);
|
||||||
.offset = attr.offset};
|
|
||||||
|
attributes[i] = vk::VertexInputAttributeDescription{.location = attrib.location,
|
||||||
|
.binding = attrib.binding,
|
||||||
|
.format = is_supported ? format
|
||||||
|
: ToVkAttributeFormat(attrib.type, 2),
|
||||||
|
.offset = attrib.offset};
|
||||||
|
|
||||||
|
// When the requested 3-component vertex format is unsupported by the hardware
|
||||||
|
// is it emulated by breaking it into a vec2 + vec1. These are combined to a vec3
|
||||||
|
// by the vertex shader.
|
||||||
|
if (!is_supported) {
|
||||||
|
const u32 location = MAX_VERTEX_ATTRIBUTES + emulated_attrib_count++;
|
||||||
|
attributes[location] = vk::VertexInputAttributeDescription{.location = location,
|
||||||
|
.binding = attrib.binding,
|
||||||
|
.format = ToVkAttributeFormat(attrib.type, 1),
|
||||||
|
.offset = attrib.offset + AttribBytes(attrib.type, 2)};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const vk::PipelineVertexInputStateCreateInfo vertex_input_info = {
|
const vk::PipelineVertexInputStateCreateInfo vertex_input_info = {
|
||||||
.vertexBindingDescriptionCount = info.vertex_layout.binding_count,
|
.vertexBindingDescriptionCount = info.vertex_layout.binding_count,
|
||||||
.pVertexBindingDescriptions = bindings.data(),
|
.pVertexBindingDescriptions = bindings.data(),
|
||||||
.vertexAttributeDescriptionCount = info.vertex_layout.attribute_count,
|
.vertexAttributeDescriptionCount = info.vertex_layout.attribute_count + emulated_attrib_count,
|
||||||
.pVertexAttributeDescriptions = attributes.data()};
|
.pVertexAttributeDescriptions = attributes.data()};
|
||||||
|
|
||||||
const vk::PipelineInputAssemblyStateCreateInfo input_assembly = {
|
const vk::PipelineInputAssemblyStateCreateInfo input_assembly = {
|
||||||
|
@@ -126,7 +126,6 @@ class DescriptorManager;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores a collection of rasterizer pipelines used during rendering.
|
* Stores a collection of rasterizer pipelines used during rendering.
|
||||||
* In addition handles descriptor set management.
|
|
||||||
*/
|
*/
|
||||||
class PipelineCache {
|
class PipelineCache {
|
||||||
public:
|
public:
|
||||||
|
@@ -109,7 +109,6 @@ RasterizerVulkan::RasterizerVulkan(Frontend::EmuWindow& emu_window, const Instan
|
|||||||
}
|
}
|
||||||
|
|
||||||
RasterizerVulkan::~RasterizerVulkan() {
|
RasterizerVulkan::~RasterizerVulkan() {
|
||||||
renderpass_cache.ExitRenderpass();
|
|
||||||
scheduler.Finish();
|
scheduler.Finish();
|
||||||
|
|
||||||
vk::Device device = instance.GetDevice();
|
vk::Device device = instance.GetDevice();
|
||||||
@@ -178,21 +177,24 @@ void RasterizerVulkan::SyncFixedState() {
|
|||||||
|
|
||||||
void RasterizerVulkan::SetupVertexArray(u32 vs_input_size, u32 vs_input_index_min,
|
void RasterizerVulkan::SetupVertexArray(u32 vs_input_size, u32 vs_input_index_min,
|
||||||
u32 vs_input_index_max) {
|
u32 vs_input_index_max) {
|
||||||
const u32 vertex_size = vs_input_size + sizeof(Common::Vec4f) * 16;
|
auto [array_ptr, array_offset, invalidate] = vertex_buffer.Map(vs_input_size, 4);
|
||||||
auto [array_ptr, array_offset, invalidate] = vertex_buffer.Map(vertex_size, 4);
|
|
||||||
|
|
||||||
// The Nintendo 3DS has 12 attribute loaders which are used to tell the GPU
|
/**
|
||||||
// how to interpret vertex data. The program firsts sets GPUREG_ATTR_BUF_BASE to the base
|
* The Nintendo 3DS has 12 attribute loaders which are used to tell the GPU
|
||||||
// address containing the vertex array data. The data for each attribute loader (i) can be found
|
* how to interpret vertex data. The program firsts sets GPUREG_ATTR_BUF_BASE to the base
|
||||||
// by adding GPUREG_ATTR_BUFi_OFFSET to the base address. Attribute loaders can be thought
|
* address containing the vertex array data. The data for each attribute loader (i) can be found
|
||||||
// as something analogous to Vulkan bindings. The user can store attributes in separate loaders
|
* by adding GPUREG_ATTR_BUFi_OFFSET to the base address. Attribute loaders can be thought
|
||||||
// or interleave them in the same loader.
|
* as something analogous to Vulkan bindings. The user can store attributes in separate loaders
|
||||||
|
* or interleave them in the same loader.
|
||||||
|
**/
|
||||||
const auto& regs = Pica::g_state.regs;
|
const auto& regs = Pica::g_state.regs;
|
||||||
const auto& vertex_attributes = regs.pipeline.vertex_attributes;
|
const auto& vertex_attributes = regs.pipeline.vertex_attributes;
|
||||||
PAddr base_address = vertex_attributes.GetPhysicalBaseAddress(); // GPUREG_ATTR_BUF_BASE
|
PAddr base_address = vertex_attributes.GetPhysicalBaseAddress(); // GPUREG_ATTR_BUF_BASE
|
||||||
|
|
||||||
std::array<bool, 16> enable_attributes{};
|
VertexLayout& layout = pipeline_info.vertex_layout;
|
||||||
VertexLayout layout{};
|
layout.attribute_count = 0;
|
||||||
|
layout.binding_count = 0;
|
||||||
|
enable_attributes.fill(false);
|
||||||
|
|
||||||
u32 buffer_offset = 0;
|
u32 buffer_offset = 0;
|
||||||
for (const auto& loader : vertex_attributes.attribute_loaders) {
|
for (const auto& loader : vertex_attributes.attribute_loaders) {
|
||||||
@@ -250,12 +252,33 @@ void RasterizerVulkan::SetupVertexArray(u32 vs_input_size, u32 vs_input_index_mi
|
|||||||
buffer_offset += Common::AlignUp(data_size, 16);
|
buffer_offset += Common::AlignUp(data_size, 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
array_ptr += buffer_offset;
|
binding_offsets[layout.binding_count] = array_offset + buffer_offset;
|
||||||
|
vertex_buffer.Commit(buffer_offset);
|
||||||
|
|
||||||
|
// Assign the rest of the attributes to the last binding
|
||||||
|
SetupFixedAttribs();
|
||||||
|
|
||||||
|
// Bind the generated bindings
|
||||||
|
scheduler.Record([this, layout = pipeline_info.vertex_layout,
|
||||||
|
offsets = binding_offsets](vk::CommandBuffer render_cmdbuf, vk::CommandBuffer) {
|
||||||
|
std::array<vk::Buffer, 16> buffers;
|
||||||
|
buffers.fill(vertex_buffer.GetHandle());
|
||||||
|
render_cmdbuf.bindVertexBuffers(0, layout.binding_count, buffers.data(),
|
||||||
|
offsets.data());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void RasterizerVulkan::SetupFixedAttribs() {
|
||||||
|
const auto& regs = Pica::g_state.regs;
|
||||||
|
const auto& vertex_attributes = regs.pipeline.vertex_attributes;
|
||||||
|
VertexLayout& layout = pipeline_info.vertex_layout;
|
||||||
|
|
||||||
|
auto [fixed_ptr, fixed_offset, _] = vertex_buffer.Map(16 * sizeof(Common::Vec4f));
|
||||||
|
|
||||||
// Reserve the last binding for fixed and default attributes
|
// Reserve the last binding for fixed and default attributes
|
||||||
// Place the default attrib at offset zero for easy access
|
// Place the default attrib at offset zero for easy access
|
||||||
const Common::Vec4f default_attrib = Common::MakeVec(0.f, 0.f, 0.f, 1.f);
|
static const Common::Vec4f default_attrib{0.f, 0.f, 0.f, 1.f};
|
||||||
std::memcpy(array_ptr, default_attrib.AsArray(), sizeof(Common::Vec4f));
|
std::memcpy(fixed_ptr, default_attrib.AsArray(), sizeof(Common::Vec4f));
|
||||||
|
|
||||||
// Find all fixed attributes and assign them to the last binding
|
// Find all fixed attributes and assign them to the last binding
|
||||||
u32 offset = sizeof(Common::Vec4f);
|
u32 offset = sizeof(Common::Vec4f);
|
||||||
@@ -268,7 +291,7 @@ void RasterizerVulkan::SetupVertexArray(u32 vs_input_size, u32 vs_input_index_mi
|
|||||||
attr.w.ToFloat32()};
|
attr.w.ToFloat32()};
|
||||||
|
|
||||||
const u32 data_size = sizeof(float) * static_cast<u32>(data.size());
|
const u32 data_size = sizeof(float) * static_cast<u32>(data.size());
|
||||||
std::memcpy(array_ptr + offset, data.data(), data_size);
|
std::memcpy(fixed_ptr + offset, data.data(), data_size);
|
||||||
|
|
||||||
VertexAttribute& attribute = layout.attributes[layout.attribute_count++];
|
VertexAttribute& attribute = layout.attributes[layout.attribute_count++];
|
||||||
attribute.binding.Assign(layout.binding_count);
|
attribute.binding.Assign(layout.binding_count);
|
||||||
@@ -299,26 +322,16 @@ void RasterizerVulkan::SetupVertexArray(u32 vs_input_size, u32 vs_input_index_mi
|
|||||||
|
|
||||||
// Define the fixed+default binding
|
// Define the fixed+default binding
|
||||||
VertexBinding& binding = layout.bindings[layout.binding_count];
|
VertexBinding& binding = layout.bindings[layout.binding_count];
|
||||||
binding.binding.Assign(layout.binding_count);
|
binding.binding.Assign(layout.binding_count++);
|
||||||
binding.fixed.Assign(1);
|
binding.fixed.Assign(1);
|
||||||
binding.stride.Assign(offset);
|
binding.stride.Assign(offset);
|
||||||
|
|
||||||
binding_offsets[layout.binding_count++] = array_offset + buffer_offset;
|
vertex_buffer.Commit(offset);
|
||||||
ASSERT(buffer_offset + offset <= vertex_size);
|
|
||||||
vertex_buffer.Commit(buffer_offset + offset);
|
|
||||||
|
|
||||||
// Update the pipeline vertex layout
|
|
||||||
pipeline_info.vertex_layout = layout;
|
|
||||||
|
|
||||||
scheduler.Record([this, layout, offsets = binding_offsets](vk::CommandBuffer render_cmdbuf, vk::CommandBuffer) {
|
|
||||||
std::array<vk::Buffer, 16> buffers;
|
|
||||||
buffers.fill(vertex_buffer.GetHandle());
|
|
||||||
render_cmdbuf.bindVertexBuffers(0, layout.binding_count, buffers.data(),
|
|
||||||
offsets.data());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MICROPROFILE_DEFINE(Vulkan_VS, "Vulkan", "Vertex Shader Setup", MP_RGB(192, 128, 128));
|
||||||
bool RasterizerVulkan::SetupVertexShader() {
|
bool RasterizerVulkan::SetupVertexShader() {
|
||||||
|
MICROPROFILE_SCOPE(Vulkan_VS);
|
||||||
return pipeline_cache.UseProgrammableVertexShader(Pica::g_state.regs, Pica::g_state.vs,
|
return pipeline_cache.UseProgrammableVertexShader(Pica::g_state.regs, Pica::g_state.vs,
|
||||||
pipeline_info.vertex_layout);
|
pipeline_info.vertex_layout);
|
||||||
}
|
}
|
||||||
@@ -354,7 +367,7 @@ bool RasterizerVulkan::AccelerateDrawBatch(bool is_indexed) {
|
|||||||
bool RasterizerVulkan::AccelerateDrawBatchInternal(bool is_indexed) {
|
bool RasterizerVulkan::AccelerateDrawBatchInternal(bool is_indexed) {
|
||||||
const auto& regs = Pica::g_state.regs;
|
const auto& regs = Pica::g_state.regs;
|
||||||
|
|
||||||
auto [vs_input_index_min, vs_input_index_max, vs_input_size] = AnalyzeVertexArray(is_indexed);
|
const auto [vs_input_index_min, vs_input_index_max, vs_input_size] = AnalyzeVertexArray(is_indexed);
|
||||||
|
|
||||||
if (vs_input_size > VERTEX_BUFFER_SIZE) {
|
if (vs_input_size > VERTEX_BUFFER_SIZE) {
|
||||||
LOG_WARNING(Render_Vulkan, "Too large vertex input size {}", vs_input_size);
|
LOG_WARNING(Render_Vulkan, "Too large vertex input size {}", vs_input_size);
|
||||||
|
@@ -158,12 +158,12 @@ private:
|
|||||||
/// Internal implementation for AccelerateDrawBatch
|
/// Internal implementation for AccelerateDrawBatch
|
||||||
bool AccelerateDrawBatchInternal(bool is_indexed);
|
bool AccelerateDrawBatchInternal(bool is_indexed);
|
||||||
|
|
||||||
/// Copies vertex data performing needed convertions and casts
|
|
||||||
void PaddedVertexCopy(u32 stride, u32 vertex_num, u8* data);
|
|
||||||
|
|
||||||
/// Setup vertex array for AccelerateDrawBatch
|
/// Setup vertex array for AccelerateDrawBatch
|
||||||
void SetupVertexArray(u32 vs_input_size, u32 vs_input_index_min, u32 vs_input_index_max);
|
void SetupVertexArray(u32 vs_input_size, u32 vs_input_index_min, u32 vs_input_index_max);
|
||||||
|
|
||||||
|
/// Setup the fixed attribute emulation in vulkan
|
||||||
|
void SetupFixedAttribs();
|
||||||
|
|
||||||
/// Setup vertex shader for AccelerateDrawBatch
|
/// Setup vertex shader for AccelerateDrawBatch
|
||||||
bool SetupVertexShader();
|
bool SetupVertexShader();
|
||||||
|
|
||||||
@@ -190,6 +190,7 @@ private:
|
|||||||
|
|
||||||
VertexLayout software_layout;
|
VertexLayout software_layout;
|
||||||
std::array<u64, 16> binding_offsets{};
|
std::array<u64, 16> binding_offsets{};
|
||||||
|
std::array<bool, 16> enable_attributes{};
|
||||||
vk::Sampler default_sampler;
|
vk::Sampler default_sampler;
|
||||||
Surface null_surface;
|
Surface null_surface;
|
||||||
Surface null_storage_surface;
|
Surface null_storage_surface;
|
||||||
|
@@ -121,11 +121,12 @@ void DescriptorPool::RefreshTick() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DescriptorPool::Allocate(std::size_t begin, std::size_t end) {
|
void DescriptorPool::Allocate(std::size_t begin, std::size_t end) {
|
||||||
|
LOG_INFO(Render_Vulkan, "Allocating new descriptor pool");
|
||||||
vk::DescriptorPool& pool = pools.emplace_back();
|
vk::DescriptorPool& pool = pools.emplace_back();
|
||||||
|
|
||||||
// Choose a sane pool size good for most games
|
// Choose a sane pool size good for most games
|
||||||
static constexpr std::array<vk::DescriptorPoolSize, 5> pool_sizes = {{
|
static constexpr std::array<vk::DescriptorPoolSize, 5> pool_sizes = {{
|
||||||
{vk::DescriptorType::eUniformBuffer, 2048},
|
{vk::DescriptorType::eUniformBuffer, 4096},
|
||||||
{vk::DescriptorType::eSampledImage, 4096},
|
{vk::DescriptorType::eSampledImage, 4096},
|
||||||
{vk::DescriptorType::eSampler, 4096},
|
{vk::DescriptorType::eSampler, 4096},
|
||||||
{vk::DescriptorType::eUniformTexelBuffer, 2048},
|
{vk::DescriptorType::eUniformTexelBuffer, 2048},
|
||||||
|
@@ -25,9 +25,9 @@ void Scheduler::CommandChunk::ExecuteAll(vk::CommandBuffer render_cmdbuf, vk::Co
|
|||||||
last = nullptr;
|
last = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
Scheduler::Scheduler(const Instance& instance, RendererVulkan& renderer)
|
Scheduler::Scheduler(const Instance& instance, RenderpassCache& renderpass_cache, RendererVulkan& renderer)
|
||||||
: instance{instance}, renderer{renderer}, master_semaphore{instance}, command_pool{instance, master_semaphore},
|
: instance{instance}, renderpass_cache{renderpass_cache}, renderer{renderer}, master_semaphore{instance},
|
||||||
use_worker_thread{Settings::values.async_command_recording} {
|
command_pool{instance, master_semaphore}, use_worker_thread{Settings::values.async_command_recording} {
|
||||||
AllocateWorkerCommandBuffers();
|
AllocateWorkerCommandBuffers();
|
||||||
if (use_worker_thread) {
|
if (use_worker_thread) {
|
||||||
AcquireNewChunk();
|
AcquireNewChunk();
|
||||||
@@ -120,6 +120,7 @@ void Scheduler::SubmitExecution(vk::Semaphore signal_semaphore, vk::Semaphore wa
|
|||||||
const u64 signal_value = master_semaphore.NextTick();
|
const u64 signal_value = master_semaphore.NextTick();
|
||||||
state = StateFlags::AllDirty;
|
state = StateFlags::AllDirty;
|
||||||
|
|
||||||
|
renderpass_cache.ExitRenderpass();
|
||||||
Record([signal_semaphore, wait_semaphore, signal_value, this]
|
Record([signal_semaphore, wait_semaphore, signal_value, this]
|
||||||
(vk::CommandBuffer render_cmdbuf, vk::CommandBuffer upload_cmdbuf) {
|
(vk::CommandBuffer render_cmdbuf, vk::CommandBuffer upload_cmdbuf) {
|
||||||
MICROPROFILE_SCOPE(Vulkan_Submit);
|
MICROPROFILE_SCOPE(Vulkan_Submit);
|
||||||
|
@@ -27,13 +27,15 @@ enum class StateFlags {
|
|||||||
DECLARE_ENUM_FLAG_OPERATORS(StateFlags)
|
DECLARE_ENUM_FLAG_OPERATORS(StateFlags)
|
||||||
|
|
||||||
class Instance;
|
class Instance;
|
||||||
|
class RenderpassCache;
|
||||||
class RendererVulkan;
|
class RendererVulkan;
|
||||||
|
|
||||||
/// The scheduler abstracts command buffer and fence management with an interface that's able to do
|
/// The scheduler abstracts command buffer and fence management with an interface that's able to do
|
||||||
/// OpenGL-like operations on Vulkan command buffers.
|
/// OpenGL-like operations on Vulkan command buffers.
|
||||||
class Scheduler {
|
class Scheduler {
|
||||||
public:
|
public:
|
||||||
explicit Scheduler(const Instance& instance, RendererVulkan& renderer);
|
explicit Scheduler(const Instance& instance, RenderpassCache& renderpass_cache,
|
||||||
|
RendererVulkan& renderer);
|
||||||
~Scheduler();
|
~Scheduler();
|
||||||
|
|
||||||
/// Sends the current execution context to the GPU.
|
/// Sends the current execution context to the GPU.
|
||||||
@@ -193,6 +195,7 @@ private:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
const Instance& instance;
|
const Instance& instance;
|
||||||
|
RenderpassCache& renderpass_cache;
|
||||||
RendererVulkan& renderer;
|
RendererVulkan& renderer;
|
||||||
MasterSemaphore master_semaphore;
|
MasterSemaphore master_semaphore;
|
||||||
CommandPool command_pool;
|
CommandPool command_pool;
|
||||||
|
@@ -1667,7 +1667,7 @@ layout (set = 0, binding = 0, std140) uniform vs_config {
|
|||||||
prefix = "u";
|
prefix = "u";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
LOG_CRITICAL(Render_Vulkan, "Unknown attrib type {}", config.state.attrib_types[i]);
|
LOG_CRITICAL(Render_Vulkan, "Unknown attrib format {}", config.state.attrib_types[i]);
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1675,12 +1675,42 @@ layout (set = 0, binding = 0, std140) uniform vs_config {
|
|||||||
fmt::format("layout(location = {0}) in {1}vec4 vs_in_typed_reg{0};\n", i, prefix);
|
fmt::format("layout(location = {0}) in {1}vec4 vs_in_typed_reg{0};\n", i, prefix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Some 3-component attributes might be emulated by breaking them to vec2 + scalar.
|
||||||
|
// Define them here and combine them below
|
||||||
|
for (std::size_t i = 0; i < used_regs.size(); ++i) {
|
||||||
|
if (const u32 location = config.state.emulated_attrib_locations[i]; location != 0 && used_regs[i]) {
|
||||||
|
std::string_view type;
|
||||||
|
switch (config.state.attrib_types[i]) {
|
||||||
|
case Pica::PipelineRegs::VertexAttributeFormat::FLOAT:
|
||||||
|
type = "float";
|
||||||
|
break;
|
||||||
|
case Pica::PipelineRegs::VertexAttributeFormat::BYTE:
|
||||||
|
case Pica::PipelineRegs::VertexAttributeFormat::SHORT:
|
||||||
|
type = "int";
|
||||||
|
break;
|
||||||
|
case Pica::PipelineRegs::VertexAttributeFormat::UBYTE:
|
||||||
|
type = "uint";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG_CRITICAL(Render_Vulkan, "Unknown attrib format {}", config.state.attrib_types[i]);
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
out += fmt::format("layout(location = {}) in {} vs_in_typed_reg{}_part2;\n", location, type, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
out += '\n';
|
out += '\n';
|
||||||
|
|
||||||
// cast input registers to float to avoid computational errors
|
// cast input registers to float to avoid computational errors
|
||||||
for (std::size_t i = 0; i < used_regs.size(); ++i) {
|
for (std::size_t i = 0; i < used_regs.size(); ++i) {
|
||||||
if (used_regs[i]) {
|
if (used_regs[i]) {
|
||||||
out += fmt::format("vec4 vs_in_reg{0} = vec4(vs_in_typed_reg{0});\n", i);
|
if (config.state.emulated_attrib_locations[i] != 0) {
|
||||||
|
out += fmt::format("vec4 vs_in_reg{0} = vec4(vec2(vs_in_typed_reg{0}), float(vs_in_typed_reg{0}_part2), 0.f);\n", i);
|
||||||
|
} else {
|
||||||
|
out += fmt::format("vec4 vs_in_reg{0} = vec4(vs_in_typed_reg{0});\n", i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out += '\n';
|
out += '\n';
|
||||||
|
@@ -157,6 +157,7 @@ struct PicaShaderConfigCommon {
|
|||||||
u32 main_offset;
|
u32 main_offset;
|
||||||
bool sanitize_mul;
|
bool sanitize_mul;
|
||||||
std::array<Pica::PipelineRegs::VertexAttributeFormat, 16> attrib_types;
|
std::array<Pica::PipelineRegs::VertexAttributeFormat, 16> attrib_types;
|
||||||
|
std::array<u8, 16> emulated_attrib_locations;
|
||||||
|
|
||||||
u32 num_outputs;
|
u32 num_outputs;
|
||||||
|
|
||||||
|
@@ -5,6 +5,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include "common/alignment.h"
|
#include "common/alignment.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
|
#include "common/microprofile.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||||
#include "video_core/renderer_vulkan/vk_stream_buffer.h"
|
#include "video_core/renderer_vulkan/vk_stream_buffer.h"
|
||||||
|
@@ -114,7 +114,7 @@ void Swapchain::Present() {
|
|||||||
.swapchainCount = 1,
|
.swapchainCount = 1,
|
||||||
.pSwapchains = &swapchain,
|
.pSwapchains = &swapchain,
|
||||||
.pImageIndices = &index};
|
.pImageIndices = &index};
|
||||||
|
MICROPROFILE_SCOPE(Vulkan_Present);
|
||||||
vk::Queue present_queue = instance.GetPresentQueue();
|
vk::Queue present_queue = instance.GetPresentQueue();
|
||||||
try {
|
try {
|
||||||
[[maybe_unused]] vk::Result result = present_queue.presentKHR(present_info);
|
[[maybe_unused]] vk::Result result = present_queue.presentKHR(present_info);
|
||||||
|
@@ -60,7 +60,7 @@ u32 UnpackDepthStencil(const StagingData& data, vk::Format dest) {
|
|||||||
return depth_offset;
|
return depth_offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr u32 UPLOAD_BUFFER_SIZE = 32 * 1024 * 1024;
|
constexpr u32 UPLOAD_BUFFER_SIZE = 64 * 1024 * 1024;
|
||||||
constexpr u32 DOWNLOAD_BUFFER_SIZE = 32 * 1024 * 1024;
|
constexpr u32 DOWNLOAD_BUFFER_SIZE = 32 * 1024 * 1024;
|
||||||
|
|
||||||
TextureRuntime::TextureRuntime(const Instance& instance, Scheduler& scheduler,
|
TextureRuntime::TextureRuntime(const Instance& instance, Scheduler& scheduler,
|
||||||
@@ -124,7 +124,6 @@ void TextureRuntime::FlushBuffers() {
|
|||||||
MICROPROFILE_DEFINE(Vulkan_Finish, "Vulkan", "Scheduler Finish", MP_RGB(52, 192, 235));
|
MICROPROFILE_DEFINE(Vulkan_Finish, "Vulkan", "Scheduler Finish", MP_RGB(52, 192, 235));
|
||||||
void TextureRuntime::Finish() {
|
void TextureRuntime::Finish() {
|
||||||
MICROPROFILE_SCOPE(Vulkan_Finish);
|
MICROPROFILE_SCOPE(Vulkan_Finish);
|
||||||
renderpass_cache.ExitRenderpass();
|
|
||||||
scheduler.Finish();
|
scheduler.Finish();
|
||||||
download_buffer.Invalidate();
|
download_buffer.Invalidate();
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user