[GPU/D3D12] Memexport from anywhere in control flow + 8/16bpp memexport
There's no limit on the number of memory exports in a shader on the real Xenos, and exports can be done anywhere, including in loops. Now, instead of deferring the exports to the end of the shader, and assuming that export allocs are executed only once, Xenia flushes exports when it reaches an alloc (allocs terminate memory exports on Xenos, as well as individual ALU instructions with `serialize`, but not handling this case for simplicity, it's only truly mandatory to flush memory exports before starting a new one), the end of the shader, or a pixel with outstanding exports is killed. To know which eM# registers need to be flushed to the memory, traversing the successors of each exec potentially writing any eM#, and specifying that certain eM# registers might have potentially been written before each reached control flow instruction, until a flush point or the end of the shader is reached. Also, some games export to sub-32bpp formats. These are now supported via atomic AND clearing the bits of the dword to replace followed by an atomic OR inserting the new byte/short.
This commit is contained in:
@@ -177,8 +177,6 @@ void DxbcShaderTranslator::Reset() {
|
||||
|
||||
sampler_bindings_.clear();
|
||||
|
||||
memexport_alloc_current_count_ = 0;
|
||||
|
||||
std::memset(&shader_feature_info_, 0, sizeof(shader_feature_info_));
|
||||
std::memset(&statistics_, 0, sizeof(statistics_));
|
||||
}
|
||||
@@ -787,6 +785,63 @@ void DxbcShaderTranslator::StartPixelShader() {
|
||||
PopSystemTemp();
|
||||
}
|
||||
}
|
||||
|
||||
if (current_shader().memexport_eM_written()) {
|
||||
// Make sure memexport is done only once for a guest pixel.
|
||||
dxbc::Dest memexport_enabled_dest(
|
||||
dxbc::Dest::R(system_temp_memexport_enabled_and_eM_written_, 0b0001));
|
||||
dxbc::Src memexport_enabled_src(dxbc::Src::R(
|
||||
system_temp_memexport_enabled_and_eM_written_, dxbc::Src::kXXXX));
|
||||
uint32_t resolution_scaled_axes =
|
||||
uint32_t(draw_resolution_scale_x_ > 1) |
|
||||
(uint32_t(draw_resolution_scale_y_ > 1) << 1);
|
||||
if (resolution_scaled_axes) {
|
||||
uint32_t memexport_condition_temp = PushSystemTemp();
|
||||
// Only do memexport for one host pixel in a guest pixel - prefer the
|
||||
// host pixel closer to the center of the guest pixel, but one that's
|
||||
// covered with the half-pixel offset according to the top-left rule (1
|
||||
// for 2x because 0 isn't covered with the half-pixel offset, 1 for 3x
|
||||
// because it's the center and is covered with the half-pixel offset too).
|
||||
in_position_used_ |= resolution_scaled_axes;
|
||||
a_.OpFToU(dxbc::Dest::R(memexport_condition_temp, resolution_scaled_axes),
|
||||
dxbc::Src::V1D(in_reg_ps_position_));
|
||||
a_.OpUDiv(dxbc::Dest::Null(),
|
||||
dxbc::Dest::R(memexport_condition_temp, resolution_scaled_axes),
|
||||
dxbc::Src::R(memexport_condition_temp),
|
||||
dxbc::Src::LU(draw_resolution_scale_x_,
|
||||
draw_resolution_scale_y_, 0, 0));
|
||||
a_.OpIEq(dxbc::Dest::R(memexport_condition_temp, resolution_scaled_axes),
|
||||
dxbc::Src::R(memexport_condition_temp),
|
||||
dxbc::Src::LU(draw_resolution_scale_x_ >> 1,
|
||||
draw_resolution_scale_y_ >> 1, 0, 0));
|
||||
for (uint32_t i = 0; i < 2; ++i) {
|
||||
if (!(resolution_scaled_axes & (1 << i))) {
|
||||
continue;
|
||||
}
|
||||
a_.OpAnd(memexport_enabled_dest, memexport_enabled_src,
|
||||
dxbc::Src::R(memexport_condition_temp).Select(i));
|
||||
}
|
||||
// Release memexport_condition_temp.
|
||||
PopSystemTemp();
|
||||
}
|
||||
// With sample-rate shading (with float24 conversion), only do memexport
|
||||
// from one sample (as the shader is invoked multiple times for a pixel),
|
||||
// if SV_SampleIndex == firstbit_lo(SV_Coverage). For zero coverage,
|
||||
// firstbit_lo returns 0xFFFFFFFF.
|
||||
if (IsSampleRate()) {
|
||||
uint32_t memexport_condition_temp = PushSystemTemp();
|
||||
a_.OpFirstBitLo(dxbc::Dest::R(memexport_condition_temp, 0b0001),
|
||||
dxbc::Src::VCoverage());
|
||||
a_.OpIEq(
|
||||
dxbc::Dest::R(memexport_condition_temp, 0b0001),
|
||||
dxbc::Src::V1D(in_reg_ps_front_face_sample_index_, dxbc::Src::kYYYY),
|
||||
dxbc::Src::R(memexport_condition_temp, dxbc::Src::kXXXX));
|
||||
a_.OpAnd(memexport_enabled_dest, memexport_enabled_src,
|
||||
dxbc::Src::R(memexport_condition_temp, dxbc::Src::kXXXX));
|
||||
// Release memexport_condition_temp.
|
||||
PopSystemTemp();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DxbcShaderTranslator::StartTranslation() {
|
||||
@@ -883,34 +938,27 @@ void DxbcShaderTranslator::StartTranslation() {
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_depth_only_pixel_shader_) {
|
||||
// Allocate temporary registers for memexport addresses and data.
|
||||
std::memset(system_temps_memexport_address_, 0xFF,
|
||||
sizeof(system_temps_memexport_address_));
|
||||
std::memset(system_temps_memexport_data_, 0xFF,
|
||||
sizeof(system_temps_memexport_data_));
|
||||
system_temp_memexport_written_ = UINT32_MAX;
|
||||
const uint8_t* memexports_written = current_shader().memexport_eM_written();
|
||||
for (uint32_t i = 0; i < Shader::kMaxMemExports; ++i) {
|
||||
uint32_t memexport_alloc_written = memexports_written[i];
|
||||
if (memexport_alloc_written == 0) {
|
||||
continue;
|
||||
}
|
||||
// If memexport is used at all, allocate a register containing whether eM#
|
||||
// have actually been written to.
|
||||
if (system_temp_memexport_written_ == UINT32_MAX) {
|
||||
system_temp_memexport_written_ = PushSystemTemp(0b1111);
|
||||
}
|
||||
system_temps_memexport_address_[i] = PushSystemTemp(0b1111);
|
||||
uint32_t memexport_data_index;
|
||||
while (xe::bit_scan_forward(memexport_alloc_written,
|
||||
&memexport_data_index)) {
|
||||
memexport_alloc_written &= ~(1u << memexport_data_index);
|
||||
system_temps_memexport_data_[i][memexport_data_index] =
|
||||
PushSystemTemp();
|
||||
}
|
||||
// Allocate temporary registers for memexport.
|
||||
uint8_t memexport_eM_written = current_shader().memexport_eM_written();
|
||||
if (memexport_eM_written) {
|
||||
system_temp_memexport_enabled_and_eM_written_ = PushSystemTemp(0b0010);
|
||||
// Initialize the memexport conditional to whether the shared memory is
|
||||
// currently bound as UAV (to 0 or UINT32_MAX). It can be made narrower
|
||||
// later.
|
||||
a_.OpIBFE(
|
||||
dxbc::Dest::R(system_temp_memexport_enabled_and_eM_written_, 0b0001),
|
||||
dxbc::Src::LU(1), dxbc::Src::LU(kSysFlag_SharedMemoryIsUAV_Shift),
|
||||
LoadFlagsSystemConstant());
|
||||
system_temp_memexport_address_ = PushSystemTemp(0b1111);
|
||||
uint8_t memexport_eM_remaining = memexport_eM_written;
|
||||
uint32_t memexport_eM_index;
|
||||
while (xe::bit_scan_forward(memexport_eM_remaining, &memexport_eM_index)) {
|
||||
memexport_eM_remaining &= ~(uint8_t(1) << memexport_eM_index);
|
||||
system_temps_memexport_data_[memexport_eM_index] = PushSystemTemp(0b1111);
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_depth_only_pixel_shader_) {
|
||||
// Allocate system temporary variables for the translated code. Since access
|
||||
// depends on the guest code (thus no guarantees), initialize everything
|
||||
// now (except for pv, it's an internal temporary variable, not accessible
|
||||
@@ -1089,27 +1137,19 @@ void DxbcShaderTranslator::CompleteShaderCode() {
|
||||
// - system_temp_grad_h_lod_.
|
||||
// - system_temp_grad_v_vfetch_address_.
|
||||
PopSystemTemp(6);
|
||||
}
|
||||
|
||||
// Write memexported data to the shared memory UAV.
|
||||
ExportToMemory();
|
||||
uint8_t memexport_eM_written = current_shader().memexport_eM_written();
|
||||
if (memexport_eM_written) {
|
||||
// Write data for the last memexport.
|
||||
ExportToMemory(
|
||||
current_shader().memexport_eM_potentially_written_before_end());
|
||||
|
||||
// Release memexport temporary registers.
|
||||
for (int i = Shader::kMaxMemExports - 1; i >= 0; --i) {
|
||||
if (system_temps_memexport_address_[i] == UINT32_MAX) {
|
||||
continue;
|
||||
}
|
||||
// Release exported data registers.
|
||||
for (int j = 4; j >= 0; --j) {
|
||||
if (system_temps_memexport_data_[i][j] != UINT32_MAX) {
|
||||
PopSystemTemp();
|
||||
}
|
||||
}
|
||||
// Release the address register.
|
||||
PopSystemTemp();
|
||||
}
|
||||
if (system_temp_memexport_written_ != UINT32_MAX) {
|
||||
PopSystemTemp();
|
||||
}
|
||||
// Release memexport temporary registers:
|
||||
// - system_temp_memexport_enabled_and_eM_written_.
|
||||
// - system_temp_memexport_address_.
|
||||
// - system_temps_memexport_data_.
|
||||
PopSystemTemp(xe::bit_count(uint32_t(memexport_eM_written)) + 2);
|
||||
}
|
||||
|
||||
// Write stage-specific epilogue.
|
||||
@@ -1512,36 +1552,22 @@ void DxbcShaderTranslator::StoreResult(const InstructionResult& result,
|
||||
dest = dxbc::Dest::R(system_temp_point_size_edge_flag_kill_vertex_);
|
||||
break;
|
||||
case InstructionStorageTarget::kExportAddress:
|
||||
// Validate memexport writes (4D5307E6 has some completely invalid ones).
|
||||
if (!can_store_memexport_address || memexport_alloc_current_count_ == 0 ||
|
||||
memexport_alloc_current_count_ > Shader::kMaxMemExports ||
|
||||
system_temps_memexport_address_[memexport_alloc_current_count_ - 1] ==
|
||||
UINT32_MAX) {
|
||||
if (!current_shader().memexport_eM_written()) {
|
||||
return;
|
||||
}
|
||||
dest = dxbc::Dest::R(
|
||||
system_temps_memexport_address_[memexport_alloc_current_count_ - 1]);
|
||||
dest = dxbc::Dest::R(system_temp_memexport_address_);
|
||||
break;
|
||||
case InstructionStorageTarget::kExportData: {
|
||||
// Validate memexport writes (4D5307E6 has some completely invalid ones).
|
||||
if (memexport_alloc_current_count_ == 0 ||
|
||||
memexport_alloc_current_count_ > Shader::kMaxMemExports ||
|
||||
system_temps_memexport_data_[memexport_alloc_current_count_ - 1]
|
||||
[result.storage_index] == UINT32_MAX) {
|
||||
return;
|
||||
}
|
||||
dest = dxbc::Dest::R(
|
||||
system_temps_memexport_data_[memexport_alloc_current_count_ - 1]
|
||||
[result.storage_index]);
|
||||
assert_not_zero(current_shader().memexport_eM_written() &
|
||||
(uint8_t(1) << result.storage_index));
|
||||
dest = dxbc::Dest::R(system_temps_memexport_data_[result.storage_index]);
|
||||
// Mark that the eM# has been written to and needs to be exported.
|
||||
assert_not_zero(used_write_mask);
|
||||
uint32_t memexport_index = memexport_alloc_current_count_ - 1;
|
||||
a_.OpOr(dxbc::Dest::R(system_temp_memexport_written_,
|
||||
1 << (memexport_index >> 2)),
|
||||
dxbc::Src::R(system_temp_memexport_written_)
|
||||
.Select(memexport_index >> 2),
|
||||
dxbc::Src::LU(uint32_t(1) << (result.storage_index +
|
||||
((memexport_index & 3) << 3))));
|
||||
a_.OpOr(
|
||||
dxbc::Dest::R(system_temp_memexport_enabled_and_eM_written_, 0b0010),
|
||||
dxbc::Src::R(system_temp_memexport_enabled_and_eM_written_,
|
||||
dxbc::Src::kYYYY),
|
||||
dxbc::Src::LU(uint8_t(1) << result.storage_index));
|
||||
} break;
|
||||
case InstructionStorageTarget::kColor:
|
||||
assert_not_zero(used_write_mask);
|
||||
@@ -1988,15 +2014,38 @@ void DxbcShaderTranslator::ProcessJumpInstruction(
|
||||
}
|
||||
|
||||
void DxbcShaderTranslator::ProcessAllocInstruction(
|
||||
const ParsedAllocInstruction& instr) {
|
||||
const ParsedAllocInstruction& instr, uint8_t export_eM) {
|
||||
bool start_memexport = instr.type == AllocType::kMemory &&
|
||||
current_shader().memexport_eM_written();
|
||||
if (export_eM || start_memexport) {
|
||||
CloseExecConditionals();
|
||||
}
|
||||
|
||||
if (emit_source_map_) {
|
||||
instruction_disassembly_buffer_.Reset();
|
||||
instr.Disassemble(&instruction_disassembly_buffer_);
|
||||
EmitInstructionDisassembly();
|
||||
}
|
||||
|
||||
if (instr.type == AllocType::kMemory) {
|
||||
++memexport_alloc_current_count_;
|
||||
if (export_eM) {
|
||||
ExportToMemory(export_eM);
|
||||
// Reset which eM# elements have been written.
|
||||
a_.OpMov(
|
||||
dxbc::Dest::R(system_temp_memexport_enabled_and_eM_written_, 0b0010),
|
||||
dxbc::Src::LU(0));
|
||||
// Break dependencies from the previous memexport.
|
||||
uint8_t export_eM_remaining = export_eM;
|
||||
uint32_t eM_index;
|
||||
while (xe::bit_scan_forward(export_eM_remaining, &eM_index)) {
|
||||
export_eM_remaining &= ~(uint8_t(1) << eM_index);
|
||||
a_.OpMov(dxbc::Dest::R(system_temps_memexport_data_[eM_index]),
|
||||
dxbc::Src::LF(0.0f));
|
||||
}
|
||||
}
|
||||
|
||||
if (start_memexport) {
|
||||
// Initialize eA to an invalid address.
|
||||
a_.OpMov(dxbc::Dest::R(system_temp_memexport_address_), dxbc::Src::LU(0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2849,7 +2898,7 @@ void DxbcShaderTranslator::WriteInputSignature() {
|
||||
// Sample index (SV_SampleIndex) for safe memexport with sample-rate
|
||||
// shading.
|
||||
size_t sample_index_position = SIZE_MAX;
|
||||
if (current_shader().is_valid_memexport_used() && IsSampleRate()) {
|
||||
if (current_shader().memexport_eM_written() && IsSampleRate()) {
|
||||
size_t sample_index_position = shader_object_.size();
|
||||
shader_object_.resize(shader_object_.size() + kParameterDwords);
|
||||
++parameter_count;
|
||||
@@ -3623,7 +3672,7 @@ void DxbcShaderTranslator::WriteShaderCode() {
|
||||
dxbc::Name::kPosition);
|
||||
}
|
||||
bool sample_rate_memexport =
|
||||
current_shader().is_valid_memexport_used() && IsSampleRate();
|
||||
current_shader().memexport_eM_written() && IsSampleRate();
|
||||
// Sample-rate shading can't be done with UAV-only rendering (sample-rate
|
||||
// shading is only needed for float24 depth conversion when using a float32
|
||||
// host depth buffer).
|
||||
|
||||
Reference in New Issue
Block a user