mirror of
https://github.com/PabloMK7/citra
synced 2024-11-15 05:08:23 +00:00
Merge pull request #2865 from wwylele/gs++
PICA: implemented geometry shader
This commit is contained in:
commit
11baa40d75
15 changed files with 594 additions and 37 deletions
|
@ -1,6 +1,7 @@
|
|||
set(SRCS
|
||||
command_processor.cpp
|
||||
debug_utils/debug_utils.cpp
|
||||
geometry_pipeline.cpp
|
||||
pica.cpp
|
||||
primitive_assembly.cpp
|
||||
regs.cpp
|
||||
|
@ -29,6 +30,7 @@ set(SRCS
|
|||
set(HEADERS
|
||||
command_processor.h
|
||||
debug_utils/debug_utils.h
|
||||
geometry_pipeline.h
|
||||
gpu_debugger.h
|
||||
pica.h
|
||||
pica_state.h
|
||||
|
|
|
@ -161,6 +161,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
|
|||
|
||||
case PICA_REG_INDEX(pipeline.vs_default_attributes_setup.index):
|
||||
g_state.immediate.current_attribute = 0;
|
||||
g_state.immediate.reset_geometry_pipeline = true;
|
||||
default_attr_counter = 0;
|
||||
break;
|
||||
|
||||
|
@ -234,16 +235,14 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
|
|||
shader_engine->Run(g_state.vs, shader_unit);
|
||||
shader_unit.WriteOutput(regs.vs, output);
|
||||
|
||||
// Send to renderer
|
||||
using Pica::Shader::OutputVertex;
|
||||
auto AddTriangle = [](const OutputVertex& v0, const OutputVertex& v1,
|
||||
const OutputVertex& v2) {
|
||||
VideoCore::g_renderer->Rasterizer()->AddTriangle(v0, v1, v2);
|
||||
};
|
||||
|
||||
g_state.primitive_assembler.SubmitVertex(
|
||||
Shader::OutputVertex::FromAttributeBuffer(regs.rasterizer, output),
|
||||
AddTriangle);
|
||||
// Send to geometry pipeline
|
||||
if (g_state.immediate.reset_geometry_pipeline) {
|
||||
g_state.geometry_pipeline.Reconfigure();
|
||||
g_state.immediate.reset_geometry_pipeline = false;
|
||||
}
|
||||
ASSERT(!g_state.geometry_pipeline.NeedIndexInput());
|
||||
g_state.geometry_pipeline.Setup(shader_engine);
|
||||
g_state.geometry_pipeline.SubmitVertex(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -321,8 +320,8 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
|
|||
// The size has been tuned for optimal balance between hit-rate and the cost of lookup
|
||||
const size_t VERTEX_CACHE_SIZE = 32;
|
||||
std::array<u16, VERTEX_CACHE_SIZE> vertex_cache_ids;
|
||||
std::array<Shader::OutputVertex, VERTEX_CACHE_SIZE> vertex_cache;
|
||||
Shader::OutputVertex output_vertex;
|
||||
std::array<Shader::AttributeBuffer, VERTEX_CACHE_SIZE> vertex_cache;
|
||||
Shader::AttributeBuffer vs_output;
|
||||
|
||||
unsigned int vertex_cache_pos = 0;
|
||||
vertex_cache_ids.fill(-1);
|
||||
|
@ -332,6 +331,11 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
|
|||
|
||||
shader_engine->SetupBatch(g_state.vs, regs.vs.main_offset);
|
||||
|
||||
g_state.geometry_pipeline.Reconfigure();
|
||||
g_state.geometry_pipeline.Setup(shader_engine);
|
||||
if (g_state.geometry_pipeline.NeedIndexInput())
|
||||
ASSERT(is_indexed);
|
||||
|
||||
for (unsigned int index = 0; index < regs.pipeline.num_vertices; ++index) {
|
||||
// Indexed rendering doesn't use the start offset
|
||||
unsigned int vertex =
|
||||
|
@ -345,6 +349,11 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
|
|||
bool vertex_cache_hit = false;
|
||||
|
||||
if (is_indexed) {
|
||||
if (g_state.geometry_pipeline.NeedIndexInput()) {
|
||||
g_state.geometry_pipeline.SubmitIndex(vertex);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (g_debug_context && Pica::g_debug_context->recorder) {
|
||||
int size = index_u16 ? 2 : 1;
|
||||
memory_accesses.AddAccess(base_address + index_info.offset + size * index,
|
||||
|
@ -353,7 +362,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
|
|||
|
||||
for (unsigned int i = 0; i < VERTEX_CACHE_SIZE; ++i) {
|
||||
if (vertex == vertex_cache_ids[i]) {
|
||||
output_vertex = vertex_cache[i];
|
||||
vs_output = vertex_cache[i];
|
||||
vertex_cache_hit = true;
|
||||
break;
|
||||
}
|
||||
|
@ -362,7 +371,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
|
|||
|
||||
if (!vertex_cache_hit) {
|
||||
// Initialize data for the current vertex
|
||||
Shader::AttributeBuffer input, output{};
|
||||
Shader::AttributeBuffer input;
|
||||
loader.LoadVertex(base_address, index, vertex, input, memory_accesses);
|
||||
|
||||
// Send to vertex shader
|
||||
|
@ -371,26 +380,17 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
|
|||
(void*)&input);
|
||||
shader_unit.LoadInput(regs.vs, input);
|
||||
shader_engine->Run(g_state.vs, shader_unit);
|
||||
shader_unit.WriteOutput(regs.vs, output);
|
||||
|
||||
// Retrieve vertex from register data
|
||||
output_vertex = Shader::OutputVertex::FromAttributeBuffer(regs.rasterizer, output);
|
||||
shader_unit.WriteOutput(regs.vs, vs_output);
|
||||
|
||||
if (is_indexed) {
|
||||
vertex_cache[vertex_cache_pos] = output_vertex;
|
||||
vertex_cache[vertex_cache_pos] = vs_output;
|
||||
vertex_cache_ids[vertex_cache_pos] = vertex;
|
||||
vertex_cache_pos = (vertex_cache_pos + 1) % VERTEX_CACHE_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
// Send to renderer
|
||||
using Pica::Shader::OutputVertex;
|
||||
auto AddTriangle = [](const OutputVertex& v0, const OutputVertex& v1,
|
||||
const OutputVertex& v2) {
|
||||
VideoCore::g_renderer->Rasterizer()->AddTriangle(v0, v1, v2);
|
||||
};
|
||||
|
||||
primitive_assembler.SubmitVertex(output_vertex, AddTriangle);
|
||||
// Send to geometry pipeline
|
||||
g_state.geometry_pipeline.SubmitVertex(vs_output);
|
||||
}
|
||||
|
||||
for (auto& range : memory_accesses.ranges) {
|
||||
|
|
274
src/video_core/geometry_pipeline.cpp
Normal file
274
src/video_core/geometry_pipeline.cpp
Normal file
|
@ -0,0 +1,274 @@
|
|||
// Copyright 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "video_core/geometry_pipeline.h"
|
||||
#include "video_core/pica_state.h"
|
||||
#include "video_core/regs.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
namespace Pica {
|
||||
|
||||
/// An attribute buffering interface for different pipeline modes
|
||||
class GeometryPipelineBackend {
|
||||
public:
|
||||
virtual ~GeometryPipelineBackend() = default;
|
||||
|
||||
/// Checks if there is no incomplete data transfer
|
||||
virtual bool IsEmpty() const = 0;
|
||||
|
||||
/// Checks if the pipeline needs a direct input from index buffer
|
||||
virtual bool NeedIndexInput() const = 0;
|
||||
|
||||
/// Submits an index from index buffer
|
||||
virtual void SubmitIndex(unsigned int val) = 0;
|
||||
|
||||
/**
|
||||
* Submits vertex attributes
|
||||
* @param input attributes of a vertex output from vertex shader
|
||||
* @return if the buffer is full and the geometry shader should be invoked
|
||||
*/
|
||||
virtual bool SubmitVertex(const Shader::AttributeBuffer& input) = 0;
|
||||
};
|
||||
|
||||
// In the Point mode, vertex attributes are sent to the input registers in the geometry shader unit.
|
||||
// The size of vertex shader outputs and geometry shader inputs are constants. Geometry shader is
|
||||
// invoked upon inputs buffer filled up by vertex shader outputs. For example, if we have a geometry
|
||||
// shader that takes 6 inputs, and the vertex shader outputs 2 attributes, it would take 3 vertices
|
||||
// for one geometry shader invocation.
|
||||
// TODO: what happens when the input size is not divisible by the output size?
|
||||
class GeometryPipeline_Point : public GeometryPipelineBackend {
|
||||
public:
|
||||
GeometryPipeline_Point(const Regs& regs, Shader::GSUnitState& unit) : regs(regs), unit(unit) {
|
||||
ASSERT(regs.pipeline.variable_primitive == 0);
|
||||
ASSERT(regs.gs.input_to_uniform == 0);
|
||||
vs_output_num = regs.pipeline.vs_outmap_total_minus_1_a + 1;
|
||||
size_t gs_input_num = regs.gs.max_input_attribute_index + 1;
|
||||
ASSERT(gs_input_num % vs_output_num == 0);
|
||||
buffer_cur = attribute_buffer.attr;
|
||||
buffer_end = attribute_buffer.attr + gs_input_num;
|
||||
}
|
||||
|
||||
bool IsEmpty() const override {
|
||||
return buffer_cur == attribute_buffer.attr;
|
||||
}
|
||||
|
||||
bool NeedIndexInput() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
void SubmitIndex(unsigned int val) override {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
bool SubmitVertex(const Shader::AttributeBuffer& input) override {
|
||||
buffer_cur = std::copy(input.attr, input.attr + vs_output_num, buffer_cur);
|
||||
if (buffer_cur == buffer_end) {
|
||||
buffer_cur = attribute_buffer.attr;
|
||||
unit.LoadInput(regs.gs, attribute_buffer);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
const Regs& regs;
|
||||
Shader::GSUnitState& unit;
|
||||
Shader::AttributeBuffer attribute_buffer;
|
||||
Math::Vec4<float24>* buffer_cur;
|
||||
Math::Vec4<float24>* buffer_end;
|
||||
unsigned int vs_output_num;
|
||||
};
|
||||
|
||||
// In VariablePrimitive mode, vertex attributes are buffered into the uniform registers in the
|
||||
// geometry shader unit. The number of vertex is variable, which is specified by the first index
|
||||
// value in the batch. This mode is usually used for subdivision.
|
||||
class GeometryPipeline_VariablePrimitive : public GeometryPipelineBackend {
|
||||
public:
|
||||
GeometryPipeline_VariablePrimitive(const Regs& regs, Shader::ShaderSetup& setup)
|
||||
: regs(regs), setup(setup) {
|
||||
ASSERT(regs.pipeline.variable_primitive == 1);
|
||||
ASSERT(regs.gs.input_to_uniform == 1);
|
||||
vs_output_num = regs.pipeline.vs_outmap_total_minus_1_a + 1;
|
||||
}
|
||||
|
||||
bool IsEmpty() const override {
|
||||
return need_index;
|
||||
}
|
||||
|
||||
bool NeedIndexInput() const override {
|
||||
return need_index;
|
||||
}
|
||||
|
||||
void SubmitIndex(unsigned int val) override {
|
||||
DEBUG_ASSERT(need_index);
|
||||
|
||||
// The number of vertex input is put to the uniform register
|
||||
float24 vertex_num = float24::FromFloat32(val);
|
||||
setup.uniforms.f[0] = Math::MakeVec(vertex_num, vertex_num, vertex_num, vertex_num);
|
||||
|
||||
// The second uniform register and so on are used for receiving input vertices
|
||||
buffer_cur = setup.uniforms.f + 1;
|
||||
|
||||
main_vertex_num = regs.pipeline.variable_vertex_main_num_minus_1 + 1;
|
||||
total_vertex_num = val;
|
||||
need_index = false;
|
||||
}
|
||||
|
||||
bool SubmitVertex(const Shader::AttributeBuffer& input) override {
|
||||
DEBUG_ASSERT(!need_index);
|
||||
if (main_vertex_num != 0) {
|
||||
// For main vertices, receive all attributes
|
||||
buffer_cur = std::copy(input.attr, input.attr + vs_output_num, buffer_cur);
|
||||
--main_vertex_num;
|
||||
} else {
|
||||
// For other vertices, only receive the first attribute (usually the position)
|
||||
*(buffer_cur++) = input.attr[0];
|
||||
}
|
||||
--total_vertex_num;
|
||||
|
||||
if (total_vertex_num == 0) {
|
||||
need_index = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
bool need_index = true;
|
||||
const Regs& regs;
|
||||
Shader::ShaderSetup& setup;
|
||||
unsigned int main_vertex_num;
|
||||
unsigned int total_vertex_num;
|
||||
Math::Vec4<float24>* buffer_cur;
|
||||
unsigned int vs_output_num;
|
||||
};
|
||||
|
||||
// In FixedPrimitive mode, vertex attributes are buffered into the uniform registers in the geometry
|
||||
// shader unit. The number of vertex per shader invocation is constant. This is usually used for
|
||||
// particle system.
|
||||
class GeometryPipeline_FixedPrimitive : public GeometryPipelineBackend {
|
||||
public:
|
||||
GeometryPipeline_FixedPrimitive(const Regs& regs, Shader::ShaderSetup& setup)
|
||||
: regs(regs), setup(setup) {
|
||||
ASSERT(regs.pipeline.variable_primitive == 0);
|
||||
ASSERT(regs.gs.input_to_uniform == 1);
|
||||
vs_output_num = regs.pipeline.vs_outmap_total_minus_1_a + 1;
|
||||
ASSERT(vs_output_num == regs.pipeline.gs_config.stride_minus_1 + 1);
|
||||
size_t vertex_num = regs.pipeline.gs_config.fixed_vertex_num_minus_1 + 1;
|
||||
buffer_cur = buffer_begin = setup.uniforms.f + regs.pipeline.gs_config.start_index;
|
||||
buffer_end = buffer_begin + vs_output_num * vertex_num;
|
||||
}
|
||||
|
||||
bool IsEmpty() const override {
|
||||
return buffer_cur == buffer_begin;
|
||||
}
|
||||
|
||||
bool NeedIndexInput() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
void SubmitIndex(unsigned int val) override {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
bool SubmitVertex(const Shader::AttributeBuffer& input) override {
|
||||
buffer_cur = std::copy(input.attr, input.attr + vs_output_num, buffer_cur);
|
||||
if (buffer_cur == buffer_end) {
|
||||
buffer_cur = buffer_begin;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
const Regs& regs;
|
||||
Shader::ShaderSetup& setup;
|
||||
Math::Vec4<float24>* buffer_begin;
|
||||
Math::Vec4<float24>* buffer_cur;
|
||||
Math::Vec4<float24>* buffer_end;
|
||||
unsigned int vs_output_num;
|
||||
};
|
||||
|
||||
GeometryPipeline::GeometryPipeline(State& state) : state(state) {}
|
||||
|
||||
GeometryPipeline::~GeometryPipeline() = default;
|
||||
|
||||
void GeometryPipeline::SetVertexHandler(Shader::VertexHandler vertex_handler) {
|
||||
this->vertex_handler = vertex_handler;
|
||||
}
|
||||
|
||||
void GeometryPipeline::Setup(Shader::ShaderEngine* shader_engine) {
|
||||
if (!backend)
|
||||
return;
|
||||
|
||||
this->shader_engine = shader_engine;
|
||||
shader_engine->SetupBatch(state.gs, state.regs.gs.main_offset);
|
||||
}
|
||||
|
||||
void GeometryPipeline::Reconfigure() {
|
||||
ASSERT(!backend || backend->IsEmpty());
|
||||
|
||||
if (state.regs.pipeline.use_gs == PipelineRegs::UseGS::No) {
|
||||
backend = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT(state.regs.pipeline.use_gs == PipelineRegs::UseGS::Yes);
|
||||
|
||||
// The following assumes that when geometry shader is in use, the shader unit 3 is configured as
|
||||
// a geometry shader unit.
|
||||
// TODO: what happens if this is not true?
|
||||
ASSERT(state.regs.pipeline.gs_unit_exclusive_configuration == 1);
|
||||
ASSERT(state.regs.gs.shader_mode == ShaderRegs::ShaderMode::GS);
|
||||
|
||||
state.gs_unit.ConfigOutput(state.regs.gs);
|
||||
|
||||
ASSERT(state.regs.pipeline.vs_outmap_total_minus_1_a ==
|
||||
state.regs.pipeline.vs_outmap_total_minus_1_b);
|
||||
|
||||
switch (state.regs.pipeline.gs_config.mode) {
|
||||
case PipelineRegs::GSMode::Point:
|
||||
backend = std::make_unique<GeometryPipeline_Point>(state.regs, state.gs_unit);
|
||||
break;
|
||||
case PipelineRegs::GSMode::VariablePrimitive:
|
||||
backend = std::make_unique<GeometryPipeline_VariablePrimitive>(state.regs, state.gs);
|
||||
break;
|
||||
case PipelineRegs::GSMode::FixedPrimitive:
|
||||
backend = std::make_unique<GeometryPipeline_FixedPrimitive>(state.regs, state.gs);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
bool GeometryPipeline::NeedIndexInput() const {
|
||||
if (!backend)
|
||||
return false;
|
||||
return backend->NeedIndexInput();
|
||||
}
|
||||
|
||||
void GeometryPipeline::SubmitIndex(unsigned int val) {
|
||||
backend->SubmitIndex(val);
|
||||
}
|
||||
|
||||
void GeometryPipeline::SubmitVertex(const Shader::AttributeBuffer& input) {
|
||||
if (!backend) {
|
||||
// No backend means the geometry shader is disabled, so we send the vertex shader output
|
||||
// directly to the primitive assembler.
|
||||
vertex_handler(input);
|
||||
} else {
|
||||
if (backend->SubmitVertex(input)) {
|
||||
shader_engine->Run(state.gs, state.gs_unit);
|
||||
|
||||
// The uniform b15 is set to true after every geometry shader invocation. This is useful
|
||||
// for the shader to know if this is the first invocation in a batch, if the program set
|
||||
// b15 to false first.
|
||||
state.gs.uniforms.b[15] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Pica
|
49
src/video_core/geometry_pipeline.h
Normal file
49
src/video_core/geometry_pipeline.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
// Copyright 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include "video_core/shader/shader.h"
|
||||
|
||||
namespace Pica {
|
||||
|
||||
struct State;
|
||||
|
||||
class GeometryPipelineBackend;
|
||||
|
||||
/// A pipeline receiving from vertex shader and sending to geometry shader and primitive assembler
|
||||
class GeometryPipeline {
|
||||
public:
|
||||
explicit GeometryPipeline(State& state);
|
||||
~GeometryPipeline();
|
||||
|
||||
/// Sets the handler for receiving vertex outputs from vertex shader
|
||||
void SetVertexHandler(Shader::VertexHandler vertex_handler);
|
||||
|
||||
/**
|
||||
* Setup the geometry shader unit if it is in use
|
||||
* @param shader_engine the shader engine for the geometry shader to run
|
||||
*/
|
||||
void Setup(Shader::ShaderEngine* shader_engine);
|
||||
|
||||
/// Reconfigures the pipeline according to current register settings
|
||||
void Reconfigure();
|
||||
|
||||
/// Checks if the pipeline needs a direct input from index buffer
|
||||
bool NeedIndexInput() const;
|
||||
|
||||
/// Submits an index from index buffer. Call this only when NeedIndexInput returns true
|
||||
void SubmitIndex(unsigned int val);
|
||||
|
||||
/// Submits vertex attributes output from vertex shader
|
||||
void SubmitVertex(const Shader::AttributeBuffer& input);
|
||||
|
||||
private:
|
||||
Shader::VertexHandler vertex_handler;
|
||||
Shader::ShaderEngine* shader_engine;
|
||||
std::unique_ptr<GeometryPipelineBackend> backend;
|
||||
State& state;
|
||||
};
|
||||
} // namespace Pica
|
|
@ -3,9 +3,11 @@
|
|||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstring>
|
||||
#include "video_core/geometry_pipeline.h"
|
||||
#include "video_core/pica.h"
|
||||
#include "video_core/pica_state.h"
|
||||
#include "video_core/regs_pipeline.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
namespace Pica {
|
||||
|
||||
|
@ -24,6 +26,23 @@ void Zero(T& o) {
|
|||
memset(&o, 0, sizeof(o));
|
||||
}
|
||||
|
||||
State::State() : geometry_pipeline(*this) {
|
||||
auto SubmitVertex = [this](const Shader::AttributeBuffer& vertex) {
|
||||
using Pica::Shader::OutputVertex;
|
||||
auto AddTriangle = [this](const OutputVertex& v0, const OutputVertex& v1,
|
||||
const OutputVertex& v2) {
|
||||
VideoCore::g_renderer->Rasterizer()->AddTriangle(v0, v1, v2);
|
||||
};
|
||||
primitive_assembler.SubmitVertex(
|
||||
Shader::OutputVertex::FromAttributeBuffer(regs.rasterizer, vertex), AddTriangle);
|
||||
};
|
||||
|
||||
auto SetWinding = [this]() { primitive_assembler.SetWinding(); };
|
||||
|
||||
g_state.gs_unit.SetVertexHandler(SubmitVertex, SetWinding);
|
||||
g_state.geometry_pipeline.SetVertexHandler(SubmitVertex);
|
||||
}
|
||||
|
||||
void State::Reset() {
|
||||
Zero(regs);
|
||||
Zero(vs);
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/vector_math.h"
|
||||
#include "video_core/geometry_pipeline.h"
|
||||
#include "video_core/primitive_assembly.h"
|
||||
#include "video_core/regs.h"
|
||||
#include "video_core/shader/shader.h"
|
||||
|
@ -16,6 +17,7 @@ namespace Pica {
|
|||
|
||||
/// Struct used to describe current Pica state
|
||||
struct State {
|
||||
State();
|
||||
void Reset();
|
||||
|
||||
/// Pica registers
|
||||
|
@ -137,8 +139,17 @@ struct State {
|
|||
Shader::AttributeBuffer input_vertex;
|
||||
// Index of the next attribute to be loaded into `input_vertex`.
|
||||
u32 current_attribute = 0;
|
||||
// Indicates the immediate mode just started and the geometry pipeline needs to reconfigure
|
||||
bool reset_geometry_pipeline = true;
|
||||
} immediate;
|
||||
|
||||
// the geometry shader needs to be kept in the global state because some shaders relie on
|
||||
// preserved register value across shader invocation.
|
||||
// TODO: also bring the three vertex shader units here and implement the shader scheduler.
|
||||
Shader::GSUnitState gs_unit;
|
||||
|
||||
GeometryPipeline geometry_pipeline;
|
||||
|
||||
// This is constructed with a dummy triangle topology
|
||||
PrimitiveAssembler<Shader::OutputVertex> primitive_assembler;
|
||||
};
|
||||
|
|
|
@ -17,15 +17,18 @@ template <typename VertexType>
|
|||
void PrimitiveAssembler<VertexType>::SubmitVertex(const VertexType& vtx,
|
||||
TriangleHandler triangle_handler) {
|
||||
switch (topology) {
|
||||
// TODO: Figure out what's different with TriangleTopology::Shader.
|
||||
case PipelineRegs::TriangleTopology::List:
|
||||
case PipelineRegs::TriangleTopology::Shader:
|
||||
if (buffer_index < 2) {
|
||||
buffer[buffer_index++] = vtx;
|
||||
} else {
|
||||
buffer_index = 0;
|
||||
|
||||
triangle_handler(buffer[0], buffer[1], vtx);
|
||||
if (topology == PipelineRegs::TriangleTopology::Shader && winding) {
|
||||
triangle_handler(buffer[1], buffer[0], vtx);
|
||||
winding = false;
|
||||
} else {
|
||||
triangle_handler(buffer[0], buffer[1], vtx);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -50,10 +53,16 @@ void PrimitiveAssembler<VertexType>::SubmitVertex(const VertexType& vtx,
|
|||
}
|
||||
}
|
||||
|
||||
template <typename VertexType>
|
||||
void PrimitiveAssembler<VertexType>::SetWinding() {
|
||||
winding = true;
|
||||
}
|
||||
|
||||
template <typename VertexType>
|
||||
void PrimitiveAssembler<VertexType>::Reset() {
|
||||
buffer_index = 0;
|
||||
strip_ready = false;
|
||||
winding = false;
|
||||
}
|
||||
|
||||
template <typename VertexType>
|
||||
|
|
|
@ -29,6 +29,12 @@ struct PrimitiveAssembler {
|
|||
*/
|
||||
void SubmitVertex(const VertexType& vtx, TriangleHandler triangle_handler);
|
||||
|
||||
/**
|
||||
* Invert the vertex order of the next triangle. Called by geometry shader emitter.
|
||||
* This only takes effect for TriangleTopology::Shader.
|
||||
*/
|
||||
void SetWinding();
|
||||
|
||||
/**
|
||||
* Resets the internal state of the PrimitiveAssembler.
|
||||
*/
|
||||
|
@ -45,6 +51,7 @@ private:
|
|||
int buffer_index;
|
||||
VertexType buffer[2];
|
||||
bool strip_ready = false;
|
||||
bool winding = false;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
|
|
@ -147,7 +147,15 @@ struct PipelineRegs {
|
|||
// Number of vertices to render
|
||||
u32 num_vertices;
|
||||
|
||||
INSERT_PADDING_WORDS(0x1);
|
||||
enum class UseGS : u32 {
|
||||
No = 0,
|
||||
Yes = 2,
|
||||
};
|
||||
|
||||
union {
|
||||
BitField<0, 2, UseGS> use_gs;
|
||||
BitField<31, 1, u32> variable_primitive;
|
||||
};
|
||||
|
||||
// The index of the first vertex to render
|
||||
u32 vertex_offset;
|
||||
|
@ -218,7 +226,29 @@ struct PipelineRegs {
|
|||
|
||||
GPUMode gpu_mode;
|
||||
|
||||
INSERT_PADDING_WORDS(0x18);
|
||||
INSERT_PADDING_WORDS(0x4);
|
||||
BitField<0, 4, u32> vs_outmap_total_minus_1_a;
|
||||
INSERT_PADDING_WORDS(0x6);
|
||||
BitField<0, 4, u32> vs_outmap_total_minus_1_b;
|
||||
|
||||
enum class GSMode : u32 {
|
||||
Point = 0,
|
||||
VariablePrimitive = 1,
|
||||
FixedPrimitive = 2,
|
||||
};
|
||||
|
||||
union {
|
||||
BitField<0, 8, GSMode> mode;
|
||||
BitField<8, 4, u32> fixed_vertex_num_minus_1;
|
||||
BitField<12, 4, u32> stride_minus_1;
|
||||
BitField<16, 4, u32> start_index;
|
||||
} gs_config;
|
||||
|
||||
INSERT_PADDING_WORDS(0x1);
|
||||
|
||||
u32 variable_vertex_main_num_minus_1;
|
||||
|
||||
INSERT_PADDING_WORDS(0x9);
|
||||
|
||||
enum class TriangleTopology : u32 {
|
||||
List = 0,
|
||||
|
|
|
@ -24,9 +24,16 @@ struct ShaderRegs {
|
|||
|
||||
INSERT_PADDING_WORDS(0x4);
|
||||
|
||||
enum ShaderMode {
|
||||
GS = 0x08,
|
||||
VS = 0xA0,
|
||||
};
|
||||
|
||||
union {
|
||||
// Number of input attributes to shader unit - 1
|
||||
BitField<0, 4, u32> max_input_attribute_index;
|
||||
BitField<8, 8, u32> input_to_uniform;
|
||||
BitField<24, 8, ShaderMode> shader_mode;
|
||||
};
|
||||
|
||||
// Offset to shader program entry point (in words)
|
||||
|
|
|
@ -21,7 +21,8 @@ namespace Pica {
|
|||
|
||||
namespace Shader {
|
||||
|
||||
OutputVertex OutputVertex::FromAttributeBuffer(const RasterizerRegs& regs, AttributeBuffer& input) {
|
||||
OutputVertex OutputVertex::FromAttributeBuffer(const RasterizerRegs& regs,
|
||||
const AttributeBuffer& input) {
|
||||
// Setup output data
|
||||
union {
|
||||
OutputVertex ret{};
|
||||
|
@ -82,6 +83,44 @@ void UnitState::WriteOutput(const ShaderRegs& config, AttributeBuffer& output) {
|
|||
}
|
||||
}
|
||||
|
||||
UnitState::UnitState(GSEmitter* emitter) : emitter_ptr(emitter) {}
|
||||
|
||||
GSEmitter::GSEmitter() {
|
||||
handlers = new Handlers;
|
||||
}
|
||||
|
||||
GSEmitter::~GSEmitter() {
|
||||
delete handlers;
|
||||
}
|
||||
|
||||
void GSEmitter::Emit(Math::Vec4<float24> (&vertex)[16]) {
|
||||
ASSERT(vertex_id < 3);
|
||||
std::copy(std::begin(vertex), std::end(vertex), buffer[vertex_id].begin());
|
||||
if (prim_emit) {
|
||||
if (winding)
|
||||
handlers->winding_setter();
|
||||
for (size_t i = 0; i < buffer.size(); ++i) {
|
||||
AttributeBuffer output;
|
||||
unsigned int output_i = 0;
|
||||
for (unsigned int reg : Common::BitSet<u32>(output_mask)) {
|
||||
output.attr[output_i++] = buffer[i][reg];
|
||||
}
|
||||
handlers->vertex_handler(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GSUnitState::GSUnitState() : UnitState(&emitter) {}
|
||||
|
||||
void GSUnitState::SetVertexHandler(VertexHandler vertex_handler, WindingSetter winding_setter) {
|
||||
emitter.handlers->vertex_handler = std::move(vertex_handler);
|
||||
emitter.handlers->winding_setter = std::move(winding_setter);
|
||||
}
|
||||
|
||||
void GSUnitState::ConfigOutput(const ShaderRegs& config) {
|
||||
emitter.output_mask = config.output_mask;
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(GPU_Shader, "GPU", "Shader", MP_RGB(50, 50, 240));
|
||||
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
#include <nihstro/shader_bytecode.h>
|
||||
#include "common/assert.h"
|
||||
|
@ -31,6 +32,12 @@ struct AttributeBuffer {
|
|||
alignas(16) Math::Vec4<float24> attr[16];
|
||||
};
|
||||
|
||||
/// Handler type for receiving vertex outputs from vertex shader or geometry shader
|
||||
using VertexHandler = std::function<void(const AttributeBuffer&)>;
|
||||
|
||||
/// Handler type for signaling to invert the vertex order of the next triangle
|
||||
using WindingSetter = std::function<void()>;
|
||||
|
||||
struct OutputVertex {
|
||||
Math::Vec4<float24> pos;
|
||||
Math::Vec4<float24> quat;
|
||||
|
@ -43,7 +50,8 @@ struct OutputVertex {
|
|||
INSERT_PADDING_WORDS(1);
|
||||
Math::Vec2<float24> tc2;
|
||||
|
||||
static OutputVertex FromAttributeBuffer(const RasterizerRegs& regs, AttributeBuffer& output);
|
||||
static OutputVertex FromAttributeBuffer(const RasterizerRegs& regs,
|
||||
const AttributeBuffer& output);
|
||||
};
|
||||
#define ASSERT_POS(var, pos) \
|
||||
static_assert(offsetof(OutputVertex, var) == pos * sizeof(float24), "Semantic at wrong " \
|
||||
|
@ -60,6 +68,29 @@ ASSERT_POS(tc2, RasterizerRegs::VSOutputAttributes::TEXCOORD2_U);
|
|||
static_assert(std::is_pod<OutputVertex>::value, "Structure is not POD");
|
||||
static_assert(sizeof(OutputVertex) == 24 * sizeof(float), "OutputVertex has invalid size");
|
||||
|
||||
/**
|
||||
* This structure contains state information for primitive emitting in geometry shader.
|
||||
*/
|
||||
struct GSEmitter {
|
||||
std::array<std::array<Math::Vec4<float24>, 16>, 3> buffer;
|
||||
u8 vertex_id;
|
||||
bool prim_emit;
|
||||
bool winding;
|
||||
u32 output_mask;
|
||||
|
||||
// Function objects are hidden behind a raw pointer to make the structure standard layout type,
|
||||
// for JIT to use offsetof to access other members.
|
||||
struct Handlers {
|
||||
VertexHandler vertex_handler;
|
||||
WindingSetter winding_setter;
|
||||
} * handlers;
|
||||
|
||||
GSEmitter();
|
||||
~GSEmitter();
|
||||
void Emit(Math::Vec4<float24> (&vertex)[16]);
|
||||
};
|
||||
static_assert(std::is_standard_layout<GSEmitter>::value, "GSEmitter is not standard layout type");
|
||||
|
||||
/**
|
||||
* This structure contains the state information that needs to be unique for a shader unit. The 3DS
|
||||
* has four shader units that process shaders in parallel. At the present, Citra only implements a
|
||||
|
@ -67,6 +98,7 @@ static_assert(sizeof(OutputVertex) == 24 * sizeof(float), "OutputVertex has inva
|
|||
* here will make it easier for us to parallelize the shader processing later.
|
||||
*/
|
||||
struct UnitState {
|
||||
explicit UnitState(GSEmitter* emitter = nullptr);
|
||||
struct Registers {
|
||||
// The registers are accessed by the shader JIT using SSE instructions, and are therefore
|
||||
// required to be 16-byte aligned.
|
||||
|
@ -82,6 +114,8 @@ struct UnitState {
|
|||
// TODO: How many bits do these actually have?
|
||||
s32 address_registers[3];
|
||||
|
||||
GSEmitter* emitter_ptr;
|
||||
|
||||
static size_t InputOffset(const SourceRegister& reg) {
|
||||
switch (reg.GetRegisterType()) {
|
||||
case RegisterType::Input:
|
||||
|
@ -125,6 +159,19 @@ struct UnitState {
|
|||
void WriteOutput(const ShaderRegs& config, AttributeBuffer& output);
|
||||
};
|
||||
|
||||
/**
|
||||
* This is an extended shader unit state that represents the special unit that can run both vertex
|
||||
* shader and geometry shader. It contains an additional primitive emitter and utilities for
|
||||
* geometry shader.
|
||||
*/
|
||||
struct GSUnitState : public UnitState {
|
||||
GSUnitState();
|
||||
void SetVertexHandler(VertexHandler vertex_handler, WindingSetter winding_setter);
|
||||
void ConfigOutput(const ShaderRegs& config);
|
||||
|
||||
GSEmitter emitter;
|
||||
};
|
||||
|
||||
struct ShaderSetup {
|
||||
struct {
|
||||
// The float uniforms are accessed by the shader JIT using SSE instructions, and are
|
||||
|
|
|
@ -636,6 +636,22 @@ static void RunInterpreter(const ShaderSetup& setup, UnitState& state, DebugData
|
|||
break;
|
||||
}
|
||||
|
||||
case OpCode::Id::EMIT: {
|
||||
GSEmitter* emitter = state.emitter_ptr;
|
||||
ASSERT_MSG(emitter, "Execute EMIT on VS");
|
||||
emitter->Emit(state.registers.output);
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCode::Id::SETEMIT: {
|
||||
GSEmitter* emitter = state.emitter_ptr;
|
||||
ASSERT_MSG(emitter, "Execute SETEMIT on VS");
|
||||
emitter->vertex_id = instr.setemit.vertex_id;
|
||||
emitter->prim_emit = instr.setemit.prim_emit != 0;
|
||||
emitter->winding = instr.setemit.winding != 0;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
LOG_ERROR(HW_GPU, "Unhandled instruction: 0x%02x (%s): 0x%08x",
|
||||
(int)instr.opcode.Value().EffectiveOpCode(),
|
||||
|
|
|
@ -75,8 +75,8 @@ const JitFunction instr_table[64] = {
|
|||
&JitShader::Compile_IF, // ifu
|
||||
&JitShader::Compile_IF, // ifc
|
||||
&JitShader::Compile_LOOP, // loop
|
||||
nullptr, // emit
|
||||
nullptr, // sete
|
||||
&JitShader::Compile_EMIT, // emit
|
||||
&JitShader::Compile_SETE, // sete
|
||||
&JitShader::Compile_JMP, // jmpc
|
||||
&JitShader::Compile_JMP, // jmpu
|
||||
&JitShader::Compile_CMP, // cmp
|
||||
|
@ -772,6 +772,51 @@ void JitShader::Compile_JMP(Instruction instr) {
|
|||
}
|
||||
}
|
||||
|
||||
static void Emit(GSEmitter* emitter, Math::Vec4<float24> (*output)[16]) {
|
||||
emitter->Emit(*output);
|
||||
}
|
||||
|
||||
void JitShader::Compile_EMIT(Instruction instr) {
|
||||
Label have_emitter, end;
|
||||
mov(rax, qword[STATE + offsetof(UnitState, emitter_ptr)]);
|
||||
test(rax, rax);
|
||||
jnz(have_emitter);
|
||||
|
||||
ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
|
||||
mov(ABI_PARAM1, reinterpret_cast<size_t>("Execute EMIT on VS"));
|
||||
CallFarFunction(*this, LogCritical);
|
||||
ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
|
||||
jmp(end);
|
||||
|
||||
L(have_emitter);
|
||||
ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
|
||||
mov(ABI_PARAM1, rax);
|
||||
mov(ABI_PARAM2, STATE);
|
||||
add(ABI_PARAM2, static_cast<Xbyak::uint32>(offsetof(UnitState, registers.output)));
|
||||
CallFarFunction(*this, Emit);
|
||||
ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
|
||||
L(end);
|
||||
}
|
||||
|
||||
void JitShader::Compile_SETE(Instruction instr) {
|
||||
Label have_emitter, end;
|
||||
mov(rax, qword[STATE + offsetof(UnitState, emitter_ptr)]);
|
||||
test(rax, rax);
|
||||
jnz(have_emitter);
|
||||
|
||||
ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
|
||||
mov(ABI_PARAM1, reinterpret_cast<size_t>("Execute SETEMIT on VS"));
|
||||
CallFarFunction(*this, LogCritical);
|
||||
ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
|
||||
jmp(end);
|
||||
|
||||
L(have_emitter);
|
||||
mov(byte[rax + offsetof(GSEmitter, vertex_id)], instr.setemit.vertex_id);
|
||||
mov(byte[rax + offsetof(GSEmitter, prim_emit)], instr.setemit.prim_emit);
|
||||
mov(byte[rax + offsetof(GSEmitter, winding)], instr.setemit.winding);
|
||||
L(end);
|
||||
}
|
||||
|
||||
void JitShader::Compile_Block(unsigned end) {
|
||||
while (program_counter < end) {
|
||||
Compile_NextInstr();
|
||||
|
|
|
@ -66,6 +66,8 @@ public:
|
|||
void Compile_JMP(Instruction instr);
|
||||
void Compile_CMP(Instruction instr);
|
||||
void Compile_MAD(Instruction instr);
|
||||
void Compile_EMIT(Instruction instr);
|
||||
void Compile_SETE(Instruction instr);
|
||||
|
||||
private:
|
||||
void Compile_Block(unsigned end);
|
||||
|
|
Loading…
Reference in a new issue