citra/src/video_core/pica_state.h

256 lines
8.1 KiB
C++
Raw Normal View History

2016-03-03 04:16:38 +01:00
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <boost/serialization/array.hpp>
#include <boost/serialization/split_member.hpp>
#include "common/bit_field.h"
#include "common/common_types.h"
#include "common/vector_math.h"
#include "core/memory.h"
pica/command_processor: build geometry pipeline and run geometry shader The geometry pipeline manages data transfer between VS, GS and primitive assembler. It has known four modes: - no GS mode: sends VS output directly to the primitive assembler (what citra currently does) - GS mode 0: sends VS output to GS input registers, and sends GS output to primitive assembler - GS mode 1: sends VS output to GS uniform registers, and sends GS output to primitive assembler. It also takes an index from the index buffer at the beginning of each primitive for determine the primitive size. - GS mode 2: similar to mode 1, but doesn't take the index and uses a fixed primitive size. hwtest shows that immediate mode also supports GS (at least for mode 0), so the geometry pipeline gets refactored into its own class for supporting both drawing mode. In the immediate mode, some games don't set the pipeline registers to a valid value until the first attribute input, so a geometry pipeline reset flag is set in `pipeline.vs_default_attributes_setup.index` trigger, and the actual pipeline reconfigure is triggered in the first attribute input. In the normal drawing mode with index buffer, the vertex cache is a little bit modified to support the geometry pipeline. Instead of OutputVertex, it now holds AttributeBuffer, which is the input to the geometry pipeline. The AttributeBuffer->OutputVertex conversion is done inside the pipeline vertex handler. The actual hardware vertex cache is believed to be implemented in a similar way (because this is the only way that makes sense). Both geometry pipeline and GS unit rely on states preservation across drawing call, so they are put into the global state. In the future, the other three vertex shader units should be also placed in the global state, and a scheduler should be implemented on top of the four units. Note that the current gs_unit already allows running VS on it in the future.
2017-08-04 16:03:17 +02:00
#include "video_core/geometry_pipeline.h"
2016-03-03 04:16:38 +01:00
#include "video_core/primitive_assembly.h"
2017-01-28 22:27:24 +01:00
#include "video_core/regs.h"
2016-03-03 04:16:38 +01:00
#include "video_core/shader/shader.h"
2019-12-27 22:07:29 +01:00
#include "video_core/video_core.h"
2016-03-03 04:16:38 +01:00
2019-08-08 06:25:24 +02:00
// Boost::serialization doesn't like union types for some reason,
// so we need to mark arrays of union values with a special serialization method
2019-12-27 22:07:29 +01:00
template <typename Value, size_t Size>
struct UnionArray : public std::array<Value, Size> {
private:
2019-12-27 22:07:29 +01:00
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
static_assert(sizeof(Value) == sizeof(u32));
2019-12-27 22:07:29 +01:00
ar&* static_cast<u32(*)[Size]>(static_cast<void*>(this->data()));
}
friend class boost::serialization::access;
};
2019-08-07 18:08:52 +02:00
2016-03-03 04:16:38 +01:00
namespace Pica {
/// Struct used to describe current Pica state
struct State {
pica/command_processor: build geometry pipeline and run geometry shader The geometry pipeline manages data transfer between VS, GS and primitive assembler. It has known four modes: - no GS mode: sends VS output directly to the primitive assembler (what citra currently does) - GS mode 0: sends VS output to GS input registers, and sends GS output to primitive assembler - GS mode 1: sends VS output to GS uniform registers, and sends GS output to primitive assembler. It also takes an index from the index buffer at the beginning of each primitive for determine the primitive size. - GS mode 2: similar to mode 1, but doesn't take the index and uses a fixed primitive size. hwtest shows that immediate mode also supports GS (at least for mode 0), so the geometry pipeline gets refactored into its own class for supporting both drawing mode. In the immediate mode, some games don't set the pipeline registers to a valid value until the first attribute input, so a geometry pipeline reset flag is set in `pipeline.vs_default_attributes_setup.index` trigger, and the actual pipeline reconfigure is triggered in the first attribute input. In the normal drawing mode with index buffer, the vertex cache is a little bit modified to support the geometry pipeline. Instead of OutputVertex, it now holds AttributeBuffer, which is the input to the geometry pipeline. The AttributeBuffer->OutputVertex conversion is done inside the pipeline vertex handler. The actual hardware vertex cache is believed to be implemented in a similar way (because this is the only way that makes sense). Both geometry pipeline and GS unit rely on states preservation across drawing call, so they are put into the global state. In the future, the other three vertex shader units should be also placed in the global state, and a scheduler should be implemented on top of the four units. Note that the current gs_unit already allows running VS on it in the future.
2017-08-04 16:03:17 +02:00
State();
void Reset();
2016-03-03 04:16:38 +01:00
/// Pica registers
Regs regs;
Shader::ShaderSetup vs;
Shader::ShaderSetup gs;
Shader::AttributeBuffer input_default_attributes;
struct ProcTex {
union ValueEntry {
u32 raw;
// LUT value, encoded as 12-bit fixed point, with 12 fraction bits
BitField<0, 12, u32> value; // 0.0.12 fixed point
// Difference between two entry values. Used for efficient interpolation.
// 0.0.12 fixed point with two's complement. The range is [-0.5, 0.5).
// Note: the type of this is different from the one of lighting LUT
BitField<12, 12, s32> difference;
float ToFloat() const {
return static_cast<float>(value) / 4095.f;
}
float DiffToFloat() const {
return static_cast<float>(difference) / 4095.f;
}
};
union ColorEntry {
u32 raw;
BitField<0, 8, u32> r;
BitField<8, 8, u32> g;
BitField<16, 8, u32> b;
BitField<24, 8, u32> a;
Common::Vec4<u8> ToVector() const {
return {static_cast<u8>(r), static_cast<u8>(g), static_cast<u8>(b),
static_cast<u8>(a)};
}
};
union ColorDifferenceEntry {
u32 raw;
BitField<0, 8, s32> r; // half of the difference between two ColorEntry
BitField<8, 8, s32> g;
BitField<16, 8, s32> b;
BitField<24, 8, s32> a;
Common::Vec4<s32> ToVector() const {
return Common::Vec4<s32>{r, g, b, a} * 2;
}
};
2019-08-08 06:25:24 +02:00
UnionArray<ValueEntry, 128> noise_table;
UnionArray<ValueEntry, 128> color_map_table;
UnionArray<ValueEntry, 128> alpha_map_table;
UnionArray<ColorEntry, 256> color_table;
UnionArray<ColorDifferenceEntry, 256> color_diff_table;
2019-08-07 18:08:52 +02:00
private:
friend class boost::serialization::access;
2019-12-27 22:07:29 +01:00
template <class Archive>
void serialize(Archive& ar, const unsigned int file_version) {
ar& noise_table;
ar& color_map_table;
ar& alpha_map_table;
ar& color_table;
ar& color_diff_table;
2019-08-07 18:08:52 +02:00
}
} proctex;
struct Lighting {
2016-03-03 04:16:38 +01:00
union LutEntry {
// Used for raw access
u32 raw;
// LUT value, encoded as 12-bit fixed point, with 12 fraction bits
BitField<0, 12, u32> value; // 0.0.12 fixed point
2016-03-03 04:16:38 +01:00
// Used for efficient interpolation.
BitField<12, 11, u32> difference; // 0.0.11 fixed point
BitField<23, 1, u32> neg_difference;
2016-03-03 04:16:38 +01:00
float ToFloat() const {
2016-03-03 04:16:38 +01:00
return static_cast<float>(value) / 4095.f;
}
float DiffToFloat() const {
float diff = static_cast<float>(difference) / 2047.f;
return neg_difference ? -diff : diff;
}
2019-08-07 18:08:52 +02:00
2019-12-27 22:07:29 +01:00
template <class Archive>
void serialize(Archive& ar, const unsigned int file_version) {
ar& raw;
2019-08-07 18:08:52 +02:00
}
2016-03-03 04:16:38 +01:00
};
2019-08-08 06:25:24 +02:00
std::array<UnionArray<LutEntry, 256>, 24> luts;
2016-03-03 04:16:38 +01:00
} lighting;
2016-05-11 13:39:28 +02:00
struct {
union LutEntry {
// Used for raw access
u32 raw;
BitField<0, 13, s32> difference; // 1.1.11 fixed point
BitField<13, 11, u32> value; // 0.0.11 fixed point
float ToFloat() const {
return static_cast<float>(value) / 2047.0f;
}
float DiffToFloat() const {
return static_cast<float>(difference) / 2047.0f;
}
2016-05-11 13:39:28 +02:00
};
2019-08-08 06:25:24 +02:00
UnionArray<LutEntry, 128> lut;
2016-05-11 13:39:28 +02:00
} fog;
2016-03-03 04:16:38 +01:00
/// Current Pica command list
struct {
2019-08-07 18:08:52 +02:00
PAddr addr; // This exists only for serialization
2016-03-03 04:16:38 +01:00
const u32* head_ptr;
const u32* current_ptr;
u32 length;
} cmd_list;
/// Struct used to describe immediate mode rendering state
struct ImmediateModeState {
// Used to buffer partial vertices for immediate-mode rendering.
Shader::AttributeBuffer input_vertex;
// Index of the next attribute to be loaded into `input_vertex`.
u32 current_attribute = 0;
pica/command_processor: build geometry pipeline and run geometry shader The geometry pipeline manages data transfer between VS, GS and primitive assembler. It has known four modes: - no GS mode: sends VS output directly to the primitive assembler (what citra currently does) - GS mode 0: sends VS output to GS input registers, and sends GS output to primitive assembler - GS mode 1: sends VS output to GS uniform registers, and sends GS output to primitive assembler. It also takes an index from the index buffer at the beginning of each primitive for determine the primitive size. - GS mode 2: similar to mode 1, but doesn't take the index and uses a fixed primitive size. hwtest shows that immediate mode also supports GS (at least for mode 0), so the geometry pipeline gets refactored into its own class for supporting both drawing mode. In the immediate mode, some games don't set the pipeline registers to a valid value until the first attribute input, so a geometry pipeline reset flag is set in `pipeline.vs_default_attributes_setup.index` trigger, and the actual pipeline reconfigure is triggered in the first attribute input. In the normal drawing mode with index buffer, the vertex cache is a little bit modified to support the geometry pipeline. Instead of OutputVertex, it now holds AttributeBuffer, which is the input to the geometry pipeline. The AttributeBuffer->OutputVertex conversion is done inside the pipeline vertex handler. The actual hardware vertex cache is believed to be implemented in a similar way (because this is the only way that makes sense). Both geometry pipeline and GS unit rely on states preservation across drawing call, so they are put into the global state. In the future, the other three vertex shader units should be also placed in the global state, and a scheduler should be implemented on top of the four units. Note that the current gs_unit already allows running VS on it in the future.
2017-08-04 16:03:17 +02:00
// Indicates the immediate mode just started and the geometry pipeline needs to reconfigure
bool reset_geometry_pipeline = true;
2019-08-07 18:08:52 +02:00
private:
friend class boost::serialization::access;
2019-12-27 22:07:29 +01:00
template <class Archive>
void serialize(Archive& ar, const unsigned int file_version) {
ar& input_vertex;
ar& current_attribute;
ar& reset_geometry_pipeline;
2019-08-07 18:08:52 +02:00
}
2016-03-03 04:16:38 +01:00
} immediate;
pica/command_processor: build geometry pipeline and run geometry shader The geometry pipeline manages data transfer between VS, GS and primitive assembler. It has known four modes: - no GS mode: sends VS output directly to the primitive assembler (what citra currently does) - GS mode 0: sends VS output to GS input registers, and sends GS output to primitive assembler - GS mode 1: sends VS output to GS uniform registers, and sends GS output to primitive assembler. It also takes an index from the index buffer at the beginning of each primitive for determine the primitive size. - GS mode 2: similar to mode 1, but doesn't take the index and uses a fixed primitive size. hwtest shows that immediate mode also supports GS (at least for mode 0), so the geometry pipeline gets refactored into its own class for supporting both drawing mode. In the immediate mode, some games don't set the pipeline registers to a valid value until the first attribute input, so a geometry pipeline reset flag is set in `pipeline.vs_default_attributes_setup.index` trigger, and the actual pipeline reconfigure is triggered in the first attribute input. In the normal drawing mode with index buffer, the vertex cache is a little bit modified to support the geometry pipeline. Instead of OutputVertex, it now holds AttributeBuffer, which is the input to the geometry pipeline. The AttributeBuffer->OutputVertex conversion is done inside the pipeline vertex handler. The actual hardware vertex cache is believed to be implemented in a similar way (because this is the only way that makes sense). Both geometry pipeline and GS unit rely on states preservation across drawing call, so they are put into the global state. In the future, the other three vertex shader units should be also placed in the global state, and a scheduler should be implemented on top of the four units. Note that the current gs_unit already allows running VS on it in the future.
2017-08-04 16:03:17 +02:00
// 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;
int vs_float_regs_counter = 0;
std::array<u32, 4> vs_uniform_write_buffer{};
int gs_float_regs_counter = 0;
std::array<u32, 4> gs_uniform_write_buffer{};
int default_attr_counter = 0;
std::array<u32, 3> default_attr_write_buffer{};
2019-08-07 18:08:52 +02:00
private:
friend class boost::serialization::access;
2019-12-27 22:07:29 +01:00
template <class Archive>
void serialize(Archive& ar, const unsigned int file_version) {
ar& regs.reg_array;
ar& vs;
ar& gs;
ar& input_default_attributes;
ar& proctex;
ar& lighting.luts;
ar& fog.lut;
ar& cmd_list.addr;
ar& cmd_list.length;
ar& immediate;
ar& gs_unit;
ar& geometry_pipeline;
ar& primitive_assembler;
ar& vs_float_regs_counter;
ar& boost::serialization::make_array(vs_uniform_write_buffer.data(),
vs_uniform_write_buffer.size());
2019-12-27 22:07:29 +01:00
ar& gs_float_regs_counter;
ar& boost::serialization::make_array(gs_uniform_write_buffer.data(),
gs_uniform_write_buffer.size());
2019-12-27 22:07:29 +01:00
ar& default_attr_counter;
ar& boost::serialization::make_array(default_attr_write_buffer.data(),
default_attr_write_buffer.size());
2019-08-07 18:08:52 +02:00
boost::serialization::split_member(ar, *this, file_version);
}
2019-12-27 22:07:29 +01:00
template <class Archive>
void save(Archive& ar, const unsigned int file_version) const {
2019-08-07 18:08:52 +02:00
ar << static_cast<u32>(cmd_list.current_ptr - cmd_list.head_ptr);
}
2019-12-27 22:07:29 +01:00
template <class Archive>
void load(Archive& ar, const unsigned int file_version) {
2019-08-07 18:08:52 +02:00
u32 offset{};
ar >> offset;
2020-03-28 20:29:29 +01:00
cmd_list.head_ptr =
reinterpret_cast<u32*>(VideoCore::g_memory->GetPhysicalPointer(cmd_list.addr));
2019-08-07 18:08:52 +02:00
cmd_list.current_ptr = cmd_list.head_ptr + offset;
}
2016-03-03 04:16:38 +01:00
};
extern State g_state; ///< Current Pica state
} // namespace Pica