[Vulkan] Primitive processor
This commit is contained in:
parent
90432cd004
commit
2800f6180a
|
@ -2,7 +2,7 @@
|
|||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
@ -13,6 +13,7 @@
|
|||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/logging.h"
|
||||
|
@ -44,7 +45,10 @@ VulkanCommandProcessor::VulkanCommandProcessor(
|
|||
VulkanCommandProcessor::~VulkanCommandProcessor() = default;
|
||||
|
||||
void VulkanCommandProcessor::TracePlaybackWroteMemory(uint32_t base_ptr,
|
||||
uint32_t length) {}
|
||||
uint32_t length) {
|
||||
shared_memory_->MemoryInvalidationCallback(base_ptr, length, true);
|
||||
primitive_processor_->MemoryInvalidationCallback(base_ptr, length, true);
|
||||
}
|
||||
|
||||
void VulkanCommandProcessor::RestoreEdramSnapshot(const void* snapshot) {}
|
||||
|
||||
|
@ -182,6 +186,13 @@ bool VulkanCommandProcessor::SetupContext() {
|
|||
return false;
|
||||
}
|
||||
|
||||
primitive_processor_ = std::make_unique<VulkanPrimitiveProcessor>(
|
||||
*register_file_, *memory_, trace_writer_, *shared_memory_, *this);
|
||||
if (!primitive_processor_->Initialize()) {
|
||||
XELOGE("Failed to initialize the geometric primitive processor");
|
||||
return false;
|
||||
}
|
||||
|
||||
render_target_cache_ =
|
||||
std::make_unique<VulkanRenderTargetCache>(*this, *register_file_);
|
||||
if (!render_target_cache_->Initialize()) {
|
||||
|
@ -285,6 +296,8 @@ void VulkanCommandProcessor::ShutdownContext() {
|
|||
|
||||
render_target_cache_.reset();
|
||||
|
||||
primitive_processor_.reset();
|
||||
|
||||
shared_memory_.reset();
|
||||
|
||||
for (const auto& pipeline_layout_pair : pipeline_layouts_) {
|
||||
|
@ -617,7 +630,13 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type,
|
|||
SCOPE_profile_cpu_f("gpu");
|
||||
#endif // XE_UI_VULKAN_FINE_GRAINED_DRAW_SCOPES
|
||||
|
||||
BeginSubmission(true);
|
||||
const RegisterFile& regs = *register_file_;
|
||||
|
||||
xenos::ModeControl edram_mode = regs.Get<reg::RB_MODECONTROL>().edram_mode;
|
||||
if (edram_mode == xenos::ModeControl::kCopy) {
|
||||
// Special copy handling.
|
||||
return IssueCopy();
|
||||
}
|
||||
|
||||
// Vertex shader analysis.
|
||||
auto vertex_shader = static_cast<VulkanShader*>(active_vertex_shader());
|
||||
|
@ -627,13 +646,30 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type,
|
|||
}
|
||||
pipeline_cache_->AnalyzeShaderUcode(*vertex_shader);
|
||||
|
||||
BeginSubmission(true);
|
||||
|
||||
// Process primitives.
|
||||
PrimitiveProcessor::ProcessingResult primitive_processing_result;
|
||||
if (!primitive_processor_->Process(primitive_processing_result)) {
|
||||
return false;
|
||||
}
|
||||
if (!primitive_processing_result.host_draw_vertex_count) {
|
||||
// Nothing to draw.
|
||||
return true;
|
||||
}
|
||||
// TODO(Triang3l): Tessellation.
|
||||
if (primitive_processing_result.host_vertex_shader_type !=
|
||||
Shader::HostVertexShaderType::kVertex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(Triang3l): Get a pixel shader.
|
||||
VulkanShader* pixel_shader = nullptr;
|
||||
|
||||
// Shader modifications.
|
||||
SpirvShaderTranslator::Modification vertex_shader_modification =
|
||||
pipeline_cache_->GetCurrentVertexShaderModification(
|
||||
*vertex_shader, Shader::HostVertexShaderType::kVertex);
|
||||
*vertex_shader, primitive_processing_result.host_vertex_shader_type);
|
||||
SpirvShaderTranslator::Modification pixel_shader_modification =
|
||||
SpirvShaderTranslator::Modification(0);
|
||||
|
||||
|
@ -664,10 +700,10 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type,
|
|||
// current_graphics_pipeline_layout_.
|
||||
VkPipeline pipeline;
|
||||
const VulkanPipelineCache::PipelineLayoutProvider* pipeline_layout_provider;
|
||||
if (!pipeline_cache_->ConfigurePipeline(vertex_shader_translation,
|
||||
pixel_shader_translation,
|
||||
framebuffer_key.render_pass_key,
|
||||
pipeline, pipeline_layout_provider)) {
|
||||
if (!pipeline_cache_->ConfigurePipeline(
|
||||
vertex_shader_translation, pixel_shader_translation,
|
||||
primitive_processing_result, framebuffer_key.render_pass_key,
|
||||
pipeline, pipeline_layout_provider)) {
|
||||
return false;
|
||||
}
|
||||
deferred_command_buffer_.CmdVkBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
|
@ -703,7 +739,6 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type,
|
|||
current_graphics_pipeline_layout_ = pipeline_layout;
|
||||
}
|
||||
|
||||
const RegisterFile& regs = *register_file_;
|
||||
const ui::vulkan::VulkanProvider& provider =
|
||||
GetVulkanContext().GetVulkanProvider();
|
||||
const VkPhysicalDeviceProperties& device_properties =
|
||||
|
@ -718,7 +753,7 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type,
|
|||
// offset and is between maxViewportDimensions and viewportBoundsRange[1],
|
||||
// GetHostViewportInfo will adjust ndc_scale/ndc_offset to clamp it, and the
|
||||
// clamped range will be outside the largest possible framebuffer anyway.
|
||||
// TODO(Triang3l): Possibly handle maxViewportDimensions and
|
||||
// FIXME(Triang3l): Possibly handle maxViewportDimensions and
|
||||
// viewportBoundsRange separately because when using fragment shader
|
||||
// interlocks, framebuffers are not used, while the range may be wider than
|
||||
// dimensions? Though viewport bigger than 4096 - the smallest possible
|
||||
|
@ -793,29 +828,8 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type,
|
|||
<< (vfetch_index & 63);
|
||||
}
|
||||
|
||||
// Set up the geometry.
|
||||
if (indexed) {
|
||||
uint32_t index_size =
|
||||
index_buffer_info->format == xenos::IndexFormat::kInt32
|
||||
? sizeof(uint32_t)
|
||||
: sizeof(uint16_t);
|
||||
assert_false(index_buffer_info->guest_base & (index_size - 1));
|
||||
uint32_t index_base =
|
||||
index_buffer_info->guest_base & 0x1FFFFFFF & ~(index_size - 1);
|
||||
uint32_t index_buffer_size = index_buffer_info->count * index_size;
|
||||
if (!shared_memory_->RequestRange(index_base, index_buffer_size)) {
|
||||
XELOGE(
|
||||
"Failed to request index buffer at 0x{:08X} (size {}) in the shared "
|
||||
"memory",
|
||||
index_base, index_buffer_size);
|
||||
return false;
|
||||
}
|
||||
deferred_command_buffer_.CmdVkBindIndexBuffer(
|
||||
shared_memory_->buffer(), index_base,
|
||||
index_buffer_info->format == xenos::IndexFormat::kInt32
|
||||
? VK_INDEX_TYPE_UINT32
|
||||
: VK_INDEX_TYPE_UINT16);
|
||||
}
|
||||
// Insert the shared memory barrier if needed.
|
||||
// TODO(Triang3l): Memory export.
|
||||
shared_memory_->Use(VulkanSharedMemory::Usage::kRead);
|
||||
|
||||
// After all commands that may dispatch or copy, enter the render pass before
|
||||
|
@ -843,10 +857,35 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type,
|
|||
}
|
||||
|
||||
// Draw.
|
||||
if (indexed) {
|
||||
deferred_command_buffer_.CmdVkDrawIndexed(index_count, 1, 0, 0, 0);
|
||||
if (primitive_processing_result.index_buffer_type ==
|
||||
PrimitiveProcessor::ProcessedIndexBufferType::kNone) {
|
||||
deferred_command_buffer_.CmdVkDraw(
|
||||
primitive_processing_result.host_draw_vertex_count, 1, 0, 0);
|
||||
} else {
|
||||
deferred_command_buffer_.CmdVkDraw(index_count, 1, 0, 0);
|
||||
std::pair<VkBuffer, VkDeviceSize> index_buffer;
|
||||
switch (primitive_processing_result.index_buffer_type) {
|
||||
case PrimitiveProcessor::ProcessedIndexBufferType::kGuest:
|
||||
index_buffer.first = shared_memory_->buffer();
|
||||
index_buffer.second = primitive_processing_result.guest_index_base;
|
||||
break;
|
||||
case PrimitiveProcessor::ProcessedIndexBufferType::kHostConverted:
|
||||
index_buffer = primitive_processor_->GetConvertedIndexBuffer(
|
||||
primitive_processing_result.host_index_buffer_handle);
|
||||
break;
|
||||
case PrimitiveProcessor::ProcessedIndexBufferType::kHostBuiltin:
|
||||
index_buffer = primitive_processor_->GetBuiltinIndexBuffer(
|
||||
primitive_processing_result.host_index_buffer_handle);
|
||||
break;
|
||||
default:
|
||||
assert_unhandled_case(primitive_processing_result.index_buffer_type);
|
||||
return false;
|
||||
}
|
||||
deferred_command_buffer_.CmdVkBindIndexBuffer(
|
||||
index_buffer.first, index_buffer.second,
|
||||
index_buffer_info->format == xenos::IndexFormat::kInt16
|
||||
? VK_INDEX_TYPE_UINT16
|
||||
: VK_INDEX_TYPE_UINT32);
|
||||
deferred_command_buffer_.CmdVkDrawIndexed(index_count, 1, 0, 0, 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -952,6 +991,8 @@ void VulkanCommandProcessor::CheckSubmissionFence(uint64_t await_submission) {
|
|||
}
|
||||
|
||||
shared_memory_->CompletedSubmissionUpdated();
|
||||
|
||||
primitive_processor_->CompletedSubmissionUpdated();
|
||||
}
|
||||
|
||||
void VulkanCommandProcessor::BeginSubmission(bool is_guest_command) {
|
||||
|
@ -1006,6 +1047,8 @@ void VulkanCommandProcessor::BeginSubmission(bool is_guest_command) {
|
|||
current_graphics_pipeline_ = VK_NULL_HANDLE;
|
||||
current_graphics_pipeline_layout_ = nullptr;
|
||||
current_graphics_descriptor_sets_bound_up_to_date_ = 0;
|
||||
|
||||
primitive_processor_->BeginSubmission();
|
||||
}
|
||||
|
||||
if (is_opening_frame) {
|
||||
|
@ -1029,6 +1072,8 @@ void VulkanCommandProcessor::BeginSubmission(bool is_guest_command) {
|
|||
// may be reused.
|
||||
transient_descriptor_pool_uniform_buffers_->Reclaim(frame_completed_);
|
||||
uniform_buffer_pool_->Reclaim(frame_completed_);
|
||||
|
||||
primitive_processor_->BeginFrame();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1100,9 +1145,15 @@ bool VulkanCommandProcessor::EndSubmission(bool is_swap) {
|
|||
|
||||
bool is_closing_frame = is_swap && frame_open_;
|
||||
|
||||
if (is_closing_frame) {
|
||||
primitive_processor_->EndFrame();
|
||||
}
|
||||
|
||||
if (submission_open_) {
|
||||
EndRenderPass();
|
||||
|
||||
primitive_processor_->EndSubmission();
|
||||
|
||||
shared_memory_->EndSubmission();
|
||||
|
||||
uniform_buffer_pool_->FlushWrites();
|
||||
|
@ -1255,6 +1306,8 @@ bool VulkanCommandProcessor::EndSubmission(bool is_swap) {
|
|||
device, descriptor_set_layout_pair.second, nullptr);
|
||||
}
|
||||
descriptor_set_layouts_textures_.clear();
|
||||
|
||||
primitive_processor_->ClearCache();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1288,20 +1341,21 @@ void VulkanCommandProcessor::UpdateFixedFunctionState(
|
|||
// https://github.com/freedreno/mesa/blob/master/src/mesa/drivers/dri/r200/r200_state.c
|
||||
auto pa_sc_window_offset = regs.Get<reg::PA_SC_WINDOW_OFFSET>();
|
||||
|
||||
uint32_t pixel_size_x = 1, pixel_size_y = 1;
|
||||
|
||||
// Viewport.
|
||||
VkViewport viewport;
|
||||
if (!viewport_info.xy_extent[0] || !viewport_info.xy_extent[1]) {
|
||||
viewport.x = -1;
|
||||
viewport.y = -1;
|
||||
viewport.width = 1;
|
||||
viewport.height = 1;
|
||||
} else {
|
||||
if (viewport_info.xy_extent[0] && viewport_info.xy_extent[1]) {
|
||||
viewport.x = float(viewport_info.xy_offset[0]);
|
||||
viewport.y = float(viewport_info.xy_offset[1]);
|
||||
viewport.width = float(viewport_info.xy_extent[0]);
|
||||
viewport.height = float(viewport_info.xy_extent[1]);
|
||||
} else {
|
||||
// Vulkan viewport width must be greater than 0.0f, but the Xenia viewport
|
||||
// may be empty for various reasons - set the viewport to outside the
|
||||
// framebuffer.
|
||||
viewport.x = -1.0f;
|
||||
viewport.y = -1.0f;
|
||||
viewport.width = 1.0f;
|
||||
viewport.height = 1.0f;
|
||||
}
|
||||
viewport.minDepth = viewport_info.z_min;
|
||||
viewport.maxDepth = viewport_info.z_max;
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "xenia/gpu/vulkan/deferred_command_buffer.h"
|
||||
#include "xenia/gpu/vulkan/vulkan_graphics_system.h"
|
||||
#include "xenia/gpu/vulkan/vulkan_pipeline_cache.h"
|
||||
#include "xenia/gpu/vulkan/vulkan_primitive_processor.h"
|
||||
#include "xenia/gpu/vulkan/vulkan_render_target_cache.h"
|
||||
#include "xenia/gpu/vulkan/vulkan_shader.h"
|
||||
#include "xenia/gpu/vulkan/vulkan_shared_memory.h"
|
||||
|
@ -74,6 +75,9 @@ class VulkanCommandProcessor : public CommandProcessor {
|
|||
const VkSparseMemoryBind* binds,
|
||||
VkPipelineStageFlags wait_stage_mask);
|
||||
|
||||
uint64_t GetCurrentFrame() const { return frame_current_; }
|
||||
uint64_t GetCompletedFrame() const { return frame_completed_; }
|
||||
|
||||
// Must be called before doing anything outside the render pass scope,
|
||||
// including adding pipeline barriers that are not a part of the render pass
|
||||
// scope. Submission must be open.
|
||||
|
@ -247,6 +251,8 @@ class VulkanCommandProcessor : public CommandProcessor {
|
|||
|
||||
std::unique_ptr<VulkanSharedMemory> shared_memory_;
|
||||
|
||||
std::unique_ptr<VulkanPrimitiveProcessor> primitive_processor_;
|
||||
|
||||
std::unique_ptr<VulkanPipelineCache> pipeline_cache_;
|
||||
|
||||
std::unique_ptr<VulkanRenderTargetCache> render_target_cache_;
|
||||
|
|
|
@ -133,6 +133,7 @@ VulkanPipelineCache::GetCurrentPixelShaderModification(
|
|||
bool VulkanPipelineCache::ConfigurePipeline(
|
||||
VulkanShader::VulkanTranslation* vertex_shader,
|
||||
VulkanShader::VulkanTranslation* pixel_shader,
|
||||
const PrimitiveProcessor::ProcessingResult& primitive_processing_result,
|
||||
VulkanRenderTargetCache::RenderPassKey render_pass_key,
|
||||
VkPipeline& pipeline_out,
|
||||
const PipelineLayoutProvider*& pipeline_layout_out) {
|
||||
|
@ -173,7 +174,8 @@ bool VulkanPipelineCache::ConfigurePipeline(
|
|||
}
|
||||
|
||||
PipelineDescription description;
|
||||
if (!GetCurrentStateDescription(vertex_shader, pixel_shader, render_pass_key,
|
||||
if (!GetCurrentStateDescription(vertex_shader, pixel_shader,
|
||||
primitive_processing_result, render_pass_key,
|
||||
description)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -232,13 +234,13 @@ bool VulkanPipelineCache::TranslateAnalyzedShader(
|
|||
bool VulkanPipelineCache::GetCurrentStateDescription(
|
||||
const VulkanShader::VulkanTranslation* vertex_shader,
|
||||
const VulkanShader::VulkanTranslation* pixel_shader,
|
||||
const PrimitiveProcessor::ProcessingResult& primitive_processing_result,
|
||||
VulkanRenderTargetCache::RenderPassKey render_pass_key,
|
||||
PipelineDescription& description_out) const {
|
||||
description_out.Reset();
|
||||
|
||||
const RegisterFile& regs = register_file_;
|
||||
auto pa_su_sc_mode_cntl = regs.Get<reg::PA_SU_SC_MODE_CNTL>();
|
||||
auto vgt_draw_initiator = regs.Get<reg::VGT_DRAW_INITIATOR>();
|
||||
|
||||
description_out.vertex_shader_hash =
|
||||
vertex_shader->shader().ucode_data_hash();
|
||||
|
@ -250,13 +252,8 @@ bool VulkanPipelineCache::GetCurrentStateDescription(
|
|||
}
|
||||
description_out.render_pass_key = render_pass_key;
|
||||
|
||||
xenos::PrimitiveType primitive_type = vgt_draw_initiator.prim_type;
|
||||
PipelinePrimitiveTopology primitive_topology;
|
||||
// Vulkan explicitly allows primitive restart only for specific primitive
|
||||
// types, unlike Direct3D where it's valid for non-strips, but has
|
||||
// implementation-defined behavior.
|
||||
bool primitive_restart_allowed = false;
|
||||
switch (primitive_type) {
|
||||
switch (primitive_processing_result.host_primitive_type) {
|
||||
case xenos::PrimitiveType::kPointList:
|
||||
primitive_topology = PipelinePrimitiveTopology::kPointList;
|
||||
break;
|
||||
|
@ -265,23 +262,19 @@ bool VulkanPipelineCache::GetCurrentStateDescription(
|
|||
break;
|
||||
case xenos::PrimitiveType::kLineStrip:
|
||||
primitive_topology = PipelinePrimitiveTopology::kLineStrip;
|
||||
primitive_restart_allowed = true;
|
||||
break;
|
||||
case xenos::PrimitiveType::kTriangleList:
|
||||
case xenos::PrimitiveType::kRectangleList:
|
||||
primitive_topology = PipelinePrimitiveTopology::kTriangleList;
|
||||
break;
|
||||
case xenos::PrimitiveType::kTriangleFan:
|
||||
if (device_pipeline_features_.triangle_fans) {
|
||||
primitive_topology = PipelinePrimitiveTopology::kTriangleFan;
|
||||
primitive_restart_allowed = true;
|
||||
} else {
|
||||
primitive_topology = PipelinePrimitiveTopology::kTriangleList;
|
||||
}
|
||||
primitive_topology = PipelinePrimitiveTopology::kTriangleFan;
|
||||
break;
|
||||
case xenos::PrimitiveType::kTriangleStrip:
|
||||
primitive_topology = PipelinePrimitiveTopology::kTriangleStrip;
|
||||
primitive_restart_allowed = true;
|
||||
break;
|
||||
case xenos::PrimitiveType::kQuadList:
|
||||
primitive_topology = PipelinePrimitiveTopology::kLineListWithAdjacency;
|
||||
break;
|
||||
default:
|
||||
// TODO(Triang3l): All primitive types and tessellation.
|
||||
|
@ -289,7 +282,7 @@ bool VulkanPipelineCache::GetCurrentStateDescription(
|
|||
}
|
||||
description_out.primitive_topology = primitive_topology;
|
||||
description_out.primitive_restart =
|
||||
primitive_restart_allowed && pa_su_sc_mode_cntl.multi_prim_ib_ena;
|
||||
primitive_processing_result.host_primitive_reset_enabled;
|
||||
|
||||
// TODO(Triang3l): Tessellation.
|
||||
bool primitive_polygonal = draw_util::IsPrimitivePolygonal(regs);
|
||||
|
@ -313,6 +306,9 @@ bool VulkanPipelineCache::GetCurrentStateDescription(
|
|||
polygon_type =
|
||||
std::min(polygon_type, pa_su_sc_mode_cntl.polymode_back_ptype);
|
||||
}
|
||||
if (pa_su_sc_mode_cntl.poly_mode != xenos::PolygonModeEnable::kDualMode) {
|
||||
polygon_type = xenos::PolygonType::kTriangles;
|
||||
}
|
||||
switch (polygon_type) {
|
||||
case xenos::PolygonType::kPoints:
|
||||
// When points are not supported, use lines instead, preserving
|
||||
|
@ -418,15 +414,27 @@ bool VulkanPipelineCache::EnsurePipelineCreated(
|
|||
switch (description.primitive_topology) {
|
||||
case PipelinePrimitiveTopology::kPointList:
|
||||
input_assembly_state.topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
|
||||
assert_false(description.primitive_restart);
|
||||
if (description.primitive_restart) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case PipelinePrimitiveTopology::kLineList:
|
||||
input_assembly_state.topology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
|
||||
assert_false(description.primitive_restart);
|
||||
if (description.primitive_restart) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case PipelinePrimitiveTopology::kLineStrip:
|
||||
input_assembly_state.topology = VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
|
||||
break;
|
||||
case PipelinePrimitiveTopology::kTriangleList:
|
||||
input_assembly_state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
assert_false(description.primitive_restart);
|
||||
if (description.primitive_restart) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case PipelinePrimitiveTopology::kTriangleStrip:
|
||||
input_assembly_state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
|
||||
|
@ -441,9 +449,17 @@ bool VulkanPipelineCache::EnsurePipelineCreated(
|
|||
case PipelinePrimitiveTopology::kLineListWithAdjacency:
|
||||
input_assembly_state.topology =
|
||||
VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY;
|
||||
assert_false(description.primitive_restart);
|
||||
if (description.primitive_restart) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case PipelinePrimitiveTopology::kPatchList:
|
||||
input_assembly_state.topology = VK_PRIMITIVE_TOPOLOGY_PATCH_LIST;
|
||||
assert_false(description.primitive_restart);
|
||||
if (description.primitive_restart) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
assert_unhandled_case(description.primitive_topology);
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "xenia/base/hash.h"
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/base/xxhash.h"
|
||||
#include "xenia/gpu/primitive_processor.h"
|
||||
#include "xenia/gpu/register_file.h"
|
||||
#include "xenia/gpu/spirv_shader_translator.h"
|
||||
#include "xenia/gpu/vulkan/vulkan_render_target_cache.h"
|
||||
|
@ -69,11 +70,13 @@ class VulkanPipelineCache {
|
|||
const Shader& shader) const;
|
||||
|
||||
// TODO(Triang3l): Return a deferred creation handle.
|
||||
bool ConfigurePipeline(VulkanShader::VulkanTranslation* vertex_shader,
|
||||
VulkanShader::VulkanTranslation* pixel_shader,
|
||||
VulkanRenderTargetCache::RenderPassKey render_pass_key,
|
||||
VkPipeline& pipeline_out,
|
||||
const PipelineLayoutProvider*& pipeline_layout_out);
|
||||
bool ConfigurePipeline(
|
||||
VulkanShader::VulkanTranslation* vertex_shader,
|
||||
VulkanShader::VulkanTranslation* pixel_shader,
|
||||
const PrimitiveProcessor::ProcessingResult& primitive_processing_result,
|
||||
VulkanRenderTargetCache::RenderPassKey render_pass_key,
|
||||
VkPipeline& pipeline_out,
|
||||
const PipelineLayoutProvider*& pipeline_layout_out);
|
||||
|
||||
private:
|
||||
// Can only load pipeline storage if features of the device it was created on
|
||||
|
@ -168,6 +171,7 @@ class VulkanPipelineCache {
|
|||
bool GetCurrentStateDescription(
|
||||
const VulkanShader::VulkanTranslation* vertex_shader,
|
||||
const VulkanShader::VulkanTranslation* pixel_shader,
|
||||
const PrimitiveProcessor::ProcessingResult& primitive_processing_result,
|
||||
VulkanRenderTargetCache::RenderPassKey render_pass_key,
|
||||
PipelineDescription& description_out) const;
|
||||
|
||||
|
|
|
@ -0,0 +1,236 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/gpu/vulkan/vulkan_primitive_processor.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/gpu/vulkan/deferred_command_buffer.h"
|
||||
#include "xenia/gpu/vulkan/vulkan_command_processor.h"
|
||||
#include "xenia/ui/vulkan/vulkan_provider.h"
|
||||
#include "xenia/ui/vulkan/vulkan_util.h"
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
namespace vulkan {
|
||||
|
||||
VulkanPrimitiveProcessor::~VulkanPrimitiveProcessor() { Shutdown(true); }
|
||||
|
||||
bool VulkanPrimitiveProcessor::Initialize() {
|
||||
// TODO(Triang3l): fullDrawIndexUint32 feature check and indirect index fetch.
|
||||
// TODO(Triang3l): Portability subset triangleFans check when portability
|
||||
// subset support is added.
|
||||
// TODO(Triang3l): geometryShader check for quads when geometry shaders are
|
||||
// added.
|
||||
if (!InitializeCommon(true, true, false, false)) {
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
frame_index_buffer_pool_ =
|
||||
std::make_unique<ui::vulkan::VulkanUploadBufferPool>(
|
||||
command_processor_.GetVulkanContext().GetVulkanProvider(),
|
||||
VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
|
||||
std::max(size_t(kMinRequiredConvertedIndexBufferSize),
|
||||
ui::GraphicsUploadBufferPool::kDefaultPageSize));
|
||||
return true;
|
||||
}
|
||||
|
||||
void VulkanPrimitiveProcessor::Shutdown(bool from_destructor) {
|
||||
const ui::vulkan::VulkanProvider& provider =
|
||||
command_processor_.GetVulkanContext().GetVulkanProvider();
|
||||
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
|
||||
VkDevice device = provider.device();
|
||||
|
||||
frame_index_buffers_.clear();
|
||||
frame_index_buffer_pool_.reset();
|
||||
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyBuffer, device,
|
||||
builtin_index_buffer_upload_);
|
||||
ui::vulkan::util::DestroyAndNullHandle(dfn.vkFreeMemory, device,
|
||||
builtin_index_buffer_upload_memory_);
|
||||
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyBuffer, device,
|
||||
builtin_index_buffer_);
|
||||
ui::vulkan::util::DestroyAndNullHandle(dfn.vkFreeMemory, device,
|
||||
builtin_index_buffer_memory_);
|
||||
|
||||
if (!from_destructor) {
|
||||
ShutdownCommon();
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanPrimitiveProcessor::CompletedSubmissionUpdated() {
|
||||
if (builtin_index_buffer_upload_ != VK_NULL_HANDLE &&
|
||||
command_processor_.GetCompletedSubmission() >=
|
||||
builtin_index_buffer_upload_submission_) {
|
||||
const ui::vulkan::VulkanProvider& provider =
|
||||
command_processor_.GetVulkanContext().GetVulkanProvider();
|
||||
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
|
||||
VkDevice device = provider.device();
|
||||
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyBuffer, device,
|
||||
builtin_index_buffer_upload_);
|
||||
ui::vulkan::util::DestroyAndNullHandle(dfn.vkFreeMemory, device,
|
||||
builtin_index_buffer_upload_memory_);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanPrimitiveProcessor::BeginSubmission() {
|
||||
if (builtin_index_buffer_upload_ != VK_NULL_HANDLE &&
|
||||
builtin_index_buffer_upload_submission_ == UINT64_MAX) {
|
||||
// No need to submit deferred barriers - builtin_index_buffer_ has never
|
||||
// been used yet, and builtin_index_buffer_upload_ is written before
|
||||
// submitting commands reading it.
|
||||
|
||||
DeferredCommandBuffer& command_buffer =
|
||||
command_processor_.deferred_command_buffer();
|
||||
|
||||
VkBufferCopy* copy_region = command_buffer.CmdCopyBufferEmplace(
|
||||
builtin_index_buffer_upload_, builtin_index_buffer_, 1);
|
||||
copy_region->srcOffset = 0;
|
||||
copy_region->dstOffset = 0;
|
||||
copy_region->size = builtin_index_buffer_size_;
|
||||
|
||||
VkBufferMemoryBarrier builtin_index_buffer_memory_barrier;
|
||||
builtin_index_buffer_memory_barrier.sType =
|
||||
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
|
||||
builtin_index_buffer_memory_barrier.pNext = nullptr;
|
||||
builtin_index_buffer_memory_barrier.srcAccessMask =
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
builtin_index_buffer_memory_barrier.dstAccessMask =
|
||||
VK_ACCESS_INDEX_READ_BIT;
|
||||
builtin_index_buffer_memory_barrier.srcQueueFamilyIndex =
|
||||
VK_QUEUE_FAMILY_IGNORED;
|
||||
builtin_index_buffer_memory_barrier.dstQueueFamilyIndex =
|
||||
VK_QUEUE_FAMILY_IGNORED;
|
||||
builtin_index_buffer_memory_barrier.buffer = builtin_index_buffer_;
|
||||
builtin_index_buffer_memory_barrier.offset = 0;
|
||||
builtin_index_buffer_memory_barrier.size = VK_WHOLE_SIZE;
|
||||
command_buffer.CmdVkPipelineBarrier(
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0,
|
||||
0, nullptr, 1, &builtin_index_buffer_memory_barrier, 0, nullptr);
|
||||
|
||||
builtin_index_buffer_upload_submission_ =
|
||||
command_processor_.GetCurrentSubmission();
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanPrimitiveProcessor::BeginFrame() {
|
||||
frame_index_buffer_pool_->Reclaim(command_processor_.GetCompletedFrame());
|
||||
}
|
||||
|
||||
void VulkanPrimitiveProcessor::EndSubmission() {
|
||||
frame_index_buffer_pool_->FlushWrites();
|
||||
}
|
||||
|
||||
void VulkanPrimitiveProcessor::EndFrame() {
|
||||
ClearPerFrameCache();
|
||||
frame_index_buffers_.clear();
|
||||
}
|
||||
|
||||
bool VulkanPrimitiveProcessor::InitializeBuiltin16BitIndexBuffer(
|
||||
uint32_t index_count, std::function<void(uint16_t*)> fill_callback) {
|
||||
assert_not_zero(index_count);
|
||||
assert_true(builtin_index_buffer_ == VK_NULL_HANDLE);
|
||||
assert_true(builtin_index_buffer_memory_ == VK_NULL_HANDLE);
|
||||
assert_true(builtin_index_buffer_upload_ == VK_NULL_HANDLE);
|
||||
assert_true(builtin_index_buffer_upload_memory_ == VK_NULL_HANDLE);
|
||||
|
||||
const ui::vulkan::VulkanProvider& provider =
|
||||
command_processor_.GetVulkanContext().GetVulkanProvider();
|
||||
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
|
||||
VkDevice device = provider.device();
|
||||
|
||||
builtin_index_buffer_size_ = VkDeviceSize(sizeof(uint16_t) * index_count);
|
||||
if (!ui::vulkan::util::CreateDedicatedAllocationBuffer(
|
||||
provider, builtin_index_buffer_size_,
|
||||
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
|
||||
ui::vulkan::util::MemoryPurpose::kDeviceLocal, builtin_index_buffer_,
|
||||
builtin_index_buffer_memory_)) {
|
||||
XELOGE(
|
||||
"Vulkan primitive processor: Failed to create the built-in index "
|
||||
"buffer GPU resource with {} 16-bit indices",
|
||||
index_count);
|
||||
return false;
|
||||
}
|
||||
uint32_t upload_memory_type;
|
||||
if (!ui::vulkan::util::CreateDedicatedAllocationBuffer(
|
||||
provider, builtin_index_buffer_size_,
|
||||
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
|
||||
ui::vulkan::util::MemoryPurpose::kUpload,
|
||||
builtin_index_buffer_upload_, builtin_index_buffer_upload_memory_,
|
||||
&upload_memory_type)) {
|
||||
XELOGE(
|
||||
"Vulkan primitive processor: Failed to create the built-in index "
|
||||
"buffer upload resource with {} 16-bit indices",
|
||||
index_count);
|
||||
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyBuffer, device,
|
||||
builtin_index_buffer_);
|
||||
ui::vulkan::util::DestroyAndNullHandle(dfn.vkFreeMemory, device,
|
||||
builtin_index_buffer_memory_);
|
||||
return false;
|
||||
}
|
||||
|
||||
void* mapping;
|
||||
if (dfn.vkMapMemory(device, builtin_index_buffer_upload_memory_, 0,
|
||||
VK_WHOLE_SIZE, 0, &mapping) != VK_SUCCESS) {
|
||||
XELOGE(
|
||||
"Vulkan primitive processor: Failed to map the built-in index buffer "
|
||||
"upload resource with {} 16-bit indices",
|
||||
index_count);
|
||||
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyBuffer, device,
|
||||
builtin_index_buffer_upload_);
|
||||
ui::vulkan::util::DestroyAndNullHandle(dfn.vkFreeMemory, device,
|
||||
builtin_index_buffer_upload_memory_);
|
||||
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyBuffer, device,
|
||||
builtin_index_buffer_);
|
||||
ui::vulkan::util::DestroyAndNullHandle(dfn.vkFreeMemory, device,
|
||||
builtin_index_buffer_memory_);
|
||||
return false;
|
||||
}
|
||||
fill_callback(reinterpret_cast<uint16_t*>(mapping));
|
||||
ui::vulkan::util::FlushMappedMemoryRange(
|
||||
provider, builtin_index_buffer_memory_, upload_memory_type);
|
||||
dfn.vkUnmapMemory(device, builtin_index_buffer_memory_);
|
||||
|
||||
// Schedule uploading in the first submission.
|
||||
builtin_index_buffer_upload_submission_ = UINT64_MAX;
|
||||
return true;
|
||||
}
|
||||
|
||||
void* VulkanPrimitiveProcessor::RequestHostConvertedIndexBufferForCurrentFrame(
|
||||
xenos::IndexFormat format, uint32_t index_count, bool coalign_for_simd,
|
||||
uint32_t coalignment_original_address, size_t& backend_handle_out) {
|
||||
size_t index_size = format == xenos::IndexFormat::kInt16 ? sizeof(uint16_t)
|
||||
: sizeof(uint32_t);
|
||||
VkBuffer buffer;
|
||||
VkDeviceSize offset;
|
||||
uint8_t* mapping = frame_index_buffer_pool_->Request(
|
||||
command_processor_.GetCurrentFrame(),
|
||||
index_size * index_count +
|
||||
(coalign_for_simd ? XE_GPU_PRIMITIVE_PROCESSOR_SIMD_SIZE : 0),
|
||||
index_size, buffer, offset);
|
||||
if (!mapping) {
|
||||
return false;
|
||||
}
|
||||
if (coalign_for_simd) {
|
||||
ptrdiff_t coalignment_offset =
|
||||
GetSimdCoalignmentOffset(mapping, coalignment_original_address);
|
||||
mapping += coalignment_offset;
|
||||
offset = VkDeviceSize(offset + coalignment_offset);
|
||||
}
|
||||
backend_handle_out = frame_index_buffers_.size();
|
||||
frame_index_buffers_.emplace_back(buffer, offset);
|
||||
return mapping;
|
||||
}
|
||||
|
||||
} // namespace vulkan
|
||||
} // namespace gpu
|
||||
} // namespace xe
|
|
@ -0,0 +1,92 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_GPU_VULKAN_VULKAN_PRIMITIVE_PROCESSOR_H_
|
||||
#define XENIA_GPU_VULKAN_VULKAN_PRIMITIVE_PROCESSOR_H_
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/gpu/primitive_processor.h"
|
||||
#include "xenia/ui/vulkan/vulkan_provider.h"
|
||||
#include "xenia/ui/vulkan/vulkan_upload_buffer_pool.h"
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
namespace vulkan {
|
||||
|
||||
class VulkanCommandProcessor;
|
||||
|
||||
class VulkanPrimitiveProcessor final : public PrimitiveProcessor {
|
||||
public:
|
||||
VulkanPrimitiveProcessor(const RegisterFile& register_file, Memory& memory,
|
||||
TraceWriter& trace_writer,
|
||||
SharedMemory& shared_memory,
|
||||
VulkanCommandProcessor& command_processor)
|
||||
: PrimitiveProcessor(register_file, memory, trace_writer, shared_memory),
|
||||
command_processor_(command_processor) {}
|
||||
~VulkanPrimitiveProcessor();
|
||||
|
||||
bool Initialize();
|
||||
void Shutdown(bool from_destructor = false);
|
||||
void ClearCache() { frame_index_buffer_pool_->ClearCache(); }
|
||||
|
||||
void CompletedSubmissionUpdated();
|
||||
void BeginSubmission();
|
||||
void BeginFrame();
|
||||
void EndSubmission();
|
||||
void EndFrame();
|
||||
|
||||
std::pair<VkBuffer, VkDeviceSize> GetBuiltinIndexBuffer(size_t handle) const {
|
||||
assert_not_null(builtin_index_buffer_);
|
||||
return std::make_pair(
|
||||
builtin_index_buffer_,
|
||||
VkDeviceSize(GetBuiltinIndexBufferOffsetBytes(handle)));
|
||||
}
|
||||
std::pair<VkBuffer, VkDeviceSize> GetConvertedIndexBuffer(
|
||||
size_t handle) const {
|
||||
return frame_index_buffers_[handle];
|
||||
}
|
||||
|
||||
protected:
|
||||
bool InitializeBuiltin16BitIndexBuffer(
|
||||
uint32_t index_count,
|
||||
std::function<void(uint16_t*)> fill_callback) override;
|
||||
|
||||
void* RequestHostConvertedIndexBufferForCurrentFrame(
|
||||
xenos::IndexFormat format, uint32_t index_count, bool coalign_for_simd,
|
||||
uint32_t coalignment_original_address,
|
||||
size_t& backend_handle_out) override;
|
||||
|
||||
private:
|
||||
VulkanCommandProcessor& command_processor_;
|
||||
|
||||
VkDeviceSize builtin_index_buffer_size_ = 0;
|
||||
VkBuffer builtin_index_buffer_ = VK_NULL_HANDLE;
|
||||
VkDeviceMemory builtin_index_buffer_memory_ = VK_NULL_HANDLE;
|
||||
// Temporary buffer copied in the beginning of the first submission for
|
||||
// uploading to builtin_index_buffer_, destroyed when the submission when it
|
||||
// was uploaded is completed.
|
||||
VkBuffer builtin_index_buffer_upload_ = VK_NULL_HANDLE;
|
||||
VkDeviceMemory builtin_index_buffer_upload_memory_ = VK_NULL_HANDLE;
|
||||
// UINT64_MAX means not uploaded yet and needs uploading in the first
|
||||
// submission (if the upload buffer exists at all).
|
||||
uint64_t builtin_index_buffer_upload_submission_ = UINT64_MAX;
|
||||
|
||||
std::unique_ptr<ui::vulkan::VulkanUploadBufferPool> frame_index_buffer_pool_;
|
||||
// Indexed by the backend handles.
|
||||
std::deque<std::pair<VkBuffer, VkDeviceSize>> frame_index_buffers_;
|
||||
};
|
||||
|
||||
} // namespace vulkan
|
||||
} // namespace gpu
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_GPU_VULKAN_VULKAN_PRIMITIVE_PROCESSOR_H_
|
Loading…
Reference in New Issue