mirror of https://git.suyu.dev/suyu/suyu
				
				
				
			
						commit
						11baa40d75
					
				@ -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
 | 
			
		||||
@ -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
 | 
			
		||||
					Loading…
					
					
				
		Reference in New Issue