yuzu/src/video_core/engines/maxwell_3d.cpp
Subv 7b6868e908 GPU: Implement the BindStorageBuffer macro method in HLE.
This macro binds the SSBO Info Buffer as the current ConstBuffer.
This buffer is usually bound to c0 during shader execution.
Games seem to use this macro instead of directly writing the address for some reason.
2018-03-18 16:50:42 -05:00

255 lines
9.5 KiB
C++

// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/assert.h"
#include "video_core/engines/maxwell_3d.h"
namespace Tegra {
namespace Engines {
/// First register id that is actually a Macro call.
constexpr u32 MacroRegistersStart = 0xE00;
const std::unordered_map<u32, Maxwell3D::MethodInfo> Maxwell3D::method_handlers = {
{0xE24, {"SetShader", 5, &Maxwell3D::SetShader}},
{0xE2A, {"BindStorageBuffer", 1, &Maxwell3D::BindStorageBuffer}},
};
Maxwell3D::Maxwell3D(MemoryManager& memory_manager) : memory_manager(memory_manager) {}
void Maxwell3D::SubmitMacroCode(u32 entry, std::vector<u32> code) {
uploaded_macros[entry * 2 + MacroRegistersStart] = std::move(code);
}
void Maxwell3D::CallMacroMethod(u32 method, const std::vector<u32>& parameters) {
// TODO(Subv): Write an interpreter for the macros uploaded via registers 0x45 and 0x47
// The requested macro must have been uploaded already.
ASSERT_MSG(uploaded_macros.find(method) != uploaded_macros.end(), "Macro %08X was not uploaded",
method);
auto itr = method_handlers.find(method);
ASSERT_MSG(itr != method_handlers.end(), "Unhandled method call %08X", method);
ASSERT(itr->second.arguments == parameters.size());
(this->*itr->second.handler)(parameters);
// Reset the current macro and its parameters.
executing_macro = 0;
macro_params.clear();
}
void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) {
ASSERT_MSG(method < Regs::NUM_REGS,
"Invalid Maxwell3D register, increase the size of the Regs structure");
// It is an error to write to a register other than the current macro's ARG register before it
// has finished execution.
if (executing_macro != 0) {
ASSERT(method == executing_macro + 1);
}
// Methods after 0xE00 are special, they're actually triggers for some microcode that was
// uploaded to the GPU during initialization.
if (method >= MacroRegistersStart) {
// We're trying to execute a macro
if (executing_macro == 0) {
// A macro call must begin by writing the macro method's register, not its argument.
ASSERT_MSG((method % 2) == 0,
"Can't start macro execution by writing to the ARGS register");
executing_macro = method;
}
macro_params.push_back(value);
// Call the macro when there are no more parameters in the command buffer
if (remaining_params == 0) {
CallMacroMethod(executing_macro, macro_params);
}
return;
}
regs.reg_array[method] = value;
#define MAXWELL3D_REG_INDEX(field_name) (offsetof(Regs, field_name) / sizeof(u32))
switch (method) {
case MAXWELL3D_REG_INDEX(code_address.code_address_high):
case MAXWELL3D_REG_INDEX(code_address.code_address_low): {
// Note: For some reason games (like Puyo Puyo Tetris) seem to write 0 to the CODE_ADDRESS
// register, we do not currently know if that's intended or a bug, so we assert it lest
// stuff breaks in other places (like the shader address calculation).
ASSERT_MSG(regs.code_address.CodeAddress() == 0, "Unexpected CODE_ADDRESS register value.");
break;
}
case MAXWELL3D_REG_INDEX(const_buffer.cb_data[0]):
case MAXWELL3D_REG_INDEX(const_buffer.cb_data[1]):
case MAXWELL3D_REG_INDEX(const_buffer.cb_data[2]):
case MAXWELL3D_REG_INDEX(const_buffer.cb_data[3]):
case MAXWELL3D_REG_INDEX(const_buffer.cb_data[4]):
case MAXWELL3D_REG_INDEX(const_buffer.cb_data[5]):
case MAXWELL3D_REG_INDEX(const_buffer.cb_data[6]):
case MAXWELL3D_REG_INDEX(const_buffer.cb_data[7]):
case MAXWELL3D_REG_INDEX(const_buffer.cb_data[8]):
case MAXWELL3D_REG_INDEX(const_buffer.cb_data[9]):
case MAXWELL3D_REG_INDEX(const_buffer.cb_data[10]):
case MAXWELL3D_REG_INDEX(const_buffer.cb_data[11]):
case MAXWELL3D_REG_INDEX(const_buffer.cb_data[12]):
case MAXWELL3D_REG_INDEX(const_buffer.cb_data[13]):
case MAXWELL3D_REG_INDEX(const_buffer.cb_data[14]):
case MAXWELL3D_REG_INDEX(const_buffer.cb_data[15]): {
ProcessCBData(value);
break;
}
case MAXWELL3D_REG_INDEX(cb_bind[0].raw_config): {
ProcessCBBind(Regs::ShaderStage::Vertex);
break;
}
case MAXWELL3D_REG_INDEX(cb_bind[1].raw_config): {
ProcessCBBind(Regs::ShaderStage::TesselationControl);
break;
}
case MAXWELL3D_REG_INDEX(cb_bind[2].raw_config): {
ProcessCBBind(Regs::ShaderStage::TesselationEval);
break;
}
case MAXWELL3D_REG_INDEX(cb_bind[3].raw_config): {
ProcessCBBind(Regs::ShaderStage::Geometry);
break;
}
case MAXWELL3D_REG_INDEX(cb_bind[4].raw_config): {
ProcessCBBind(Regs::ShaderStage::Fragment);
break;
}
case MAXWELL3D_REG_INDEX(draw.vertex_end_gl): {
DrawArrays();
break;
}
case MAXWELL3D_REG_INDEX(query.query_get): {
ProcessQueryGet();
break;
}
default:
break;
}
#undef MAXWELL3D_REG_INDEX
}
void Maxwell3D::ProcessQueryGet() {
GPUVAddr sequence_address = regs.query.QueryAddress();
// Since the sequence address is given as a GPU VAddr, we have to convert it to an application
// VAddr before writing.
VAddr address = memory_manager.PhysicalToVirtualAddress(sequence_address);
switch (regs.query.query_get.mode) {
case Regs::QueryMode::Write: {
// Write the current query sequence to the sequence address.
u32 sequence = regs.query.query_sequence;
Memory::Write32(address, sequence);
break;
}
default:
UNIMPLEMENTED_MSG("Query mode %u not implemented", regs.query.query_get.mode.Value());
}
}
void Maxwell3D::DrawArrays() {
LOG_WARNING(HW_GPU, "Game requested a DrawArrays, ignoring");
}
void Maxwell3D::SetShader(const std::vector<u32>& parameters) {
/**
* Parameters description:
* [0] = Shader Program.
* [1] = Unknown, presumably the shader id.
* [2] = Offset to the start of the shader, after the 0x30 bytes header.
* [3] = Shader Stage.
* [4] = Const Buffer Address >> 8.
*/
auto shader_program = static_cast<Regs::ShaderProgram>(parameters[0]);
// TODO(Subv): This address is probably an offset from the CODE_ADDRESS register.
GPUVAddr address = parameters[2];
auto shader_stage = static_cast<Regs::ShaderStage>(parameters[3]);
GPUVAddr cb_address = parameters[4] << 8;
auto& shader = state.shader_programs[static_cast<size_t>(shader_program)];
shader.program = shader_program;
shader.stage = shader_stage;
shader.address = address;
// Perform the same operations as the real macro code.
// TODO(Subv): Early exit if register 0xD1C + shader_program contains the same as params[1].
auto& shader_regs = regs.shader_config[static_cast<size_t>(shader_program)];
shader_regs.start_id = address;
// TODO(Subv): Write params[1] to register 0xD1C + shader_program.
// TODO(Subv): Write params[2] to register 0xD22 + shader_program.
// Note: This value is hardcoded in the macro's code.
static constexpr u32 DefaultCBSize = 0x10000;
regs.const_buffer.cb_size = DefaultCBSize;
regs.const_buffer.cb_address_high = cb_address >> 32;
regs.const_buffer.cb_address_low = cb_address & 0xFFFFFFFF;
// Write a hardcoded 0x11 to CB_BIND, this binds the current const buffer to buffer c1[] in the
// shader. It's likely that these are the constants for the shader.
regs.cb_bind[static_cast<size_t>(shader_stage)].valid.Assign(1);
regs.cb_bind[static_cast<size_t>(shader_stage)].index.Assign(1);
ProcessCBBind(shader_stage);
}
void Maxwell3D::BindStorageBuffer(const std::vector<u32>& parameters) {
/**
* Parameters description:
* [0] = Buffer offset >> 2
*/
u32 buffer_offset = parameters[0] << 2;
// Perform the same operations as the real macro code.
// Note: This value is hardcoded in the macro's code.
static constexpr u32 DefaultCBSize = 0x5F00;
regs.const_buffer.cb_size = DefaultCBSize;
GPUVAddr address = regs.ssbo_info.BufferAddress();
regs.const_buffer.cb_address_high = address >> 32;
regs.const_buffer.cb_address_low = address & 0xFFFFFFFF;
regs.const_buffer.cb_pos = buffer_offset;
}
void Maxwell3D::ProcessCBBind(Regs::ShaderStage stage) {
// Bind the buffer currently in CB_ADDRESS to the specified index in the desired shader stage.
auto& shader = state.shader_stages[static_cast<size_t>(stage)];
auto& bind_data = regs.cb_bind[static_cast<size_t>(stage)];
auto& buffer = shader.const_buffers[bind_data.index];
buffer.enabled = bind_data.valid.Value() != 0;
buffer.index = bind_data.index;
buffer.address = regs.const_buffer.BufferAddress();
buffer.size = regs.const_buffer.cb_size;
}
void Maxwell3D::ProcessCBData(u32 value) {
// Write the input value to the current const buffer at the current position.
GPUVAddr buffer_address = regs.const_buffer.BufferAddress();
ASSERT(buffer_address != 0);
// Don't allow writing past the end of the buffer.
ASSERT(regs.const_buffer.cb_pos + sizeof(u32) <= regs.const_buffer.cb_size);
VAddr address =
memory_manager.PhysicalToVirtualAddress(buffer_address + regs.const_buffer.cb_pos);
Memory::Write32(address, value);
// Increment the current buffer position.
regs.const_buffer.cb_pos = regs.const_buffer.cb_pos + 4;
}
} // namespace Engines
} // namespace Tegra