Files
Xenia-Canary/src/xenia/gpu/dxbc_shader_translator_memexport.cc

458 lines
17 KiB
C++

/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2018 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/gpu/dxbc_shader_translator.h"
namespace xe {
namespace gpu {
using namespace ucode;
void DxbcShaderTranslator::ExportToMemory_PackFixed32(
const uint32_t* eM_temps, uint32_t eM_count, const uint32_t bits[4],
const DxbcSrc& is_integer, const DxbcSrc& is_signed) {
// Will insert with BFI - sign extension of red will be overwritten, not
// truncated.
assert_not_zero(bits[0]);
assert_true(bits[0] + bits[1] + bits[2] + bits[3] == 32);
uint32_t mask = 0;
for (uint32_t i = 0; i < 4; ++i) {
if (bits[i]) {
mask |= 1 << i;
}
}
DxbcOpIf(true, is_signed);
{
float range[4];
for (uint32_t i = 0; i < 4; ++i) {
range[i] = bits[i] ? float((uint32_t(1) << (bits[i] - 1)) - 1) : 0.0f;
}
DxbcSrc range_src(DxbcSrc::LP(range));
DxbcOpIf(false, is_integer);
for (uint32_t i = 0; i < eM_count; ++i) {
uint32_t eM_temp = eM_temps[i];
DxbcOpMul(DxbcDest::R(eM_temp, mask), DxbcSrc::R(eM_temp), range_src);
}
DxbcOpEndIf();
for (uint32_t i = 0; i < eM_count; ++i) {
DxbcDest eM_dest(DxbcDest::R(eM_temps[i], mask));
DxbcSrc eM_src(DxbcSrc::R(eM_temps[i]));
DxbcOpMax(eM_dest, eM_src, -range_src);
DxbcOpMin(eM_dest, eM_src, range_src);
}
}
DxbcOpElse();
{
float range[4];
for (uint32_t i = 0; i < 4; ++i) {
range[i] = float((uint32_t(1) << bits[i]) - 1);
}
DxbcSrc range_src(DxbcSrc::LP(range));
DxbcOpIf(false, is_integer);
for (uint32_t i = 0; i < eM_count; ++i) {
uint32_t eM_temp = eM_temps[i];
DxbcOpMul(DxbcDest::R(eM_temp, mask), DxbcSrc::R(eM_temp), range_src);
}
DxbcOpEndIf();
for (uint32_t i = 0; i < eM_count; ++i) {
DxbcDest eM_dest(DxbcDest::R(eM_temps[i], mask));
DxbcSrc eM_src(DxbcSrc::R(eM_temps[i]));
DxbcOpMax(eM_dest, eM_src, DxbcSrc::LF(0.0f));
DxbcOpMin(eM_dest, eM_src, range_src);
}
}
DxbcOpEndIf();
for (uint32_t i = 0; i < eM_count; ++i) {
uint32_t eM_temp = eM_temps[i];
// Round to the nearest integer, according to the rules of handling integer
// formats in Direct3D.
DxbcOpRoundNE(DxbcDest::R(eM_temp, mask), DxbcSrc::R(eM_temp));
DxbcOpFToI(DxbcDest::R(eM_temp, mask), DxbcSrc::R(eM_temp));
DxbcDest eM_packed_dest(DxbcDest::R(eM_temp, 0b0001));
DxbcSrc eM_packed_src(DxbcSrc::R(eM_temp, DxbcSrc::kXXXX));
uint32_t offset = bits[0];
for (uint32_t j = 1; j < 4; ++j) {
if (!bits[j]) {
continue;
}
DxbcOpBFI(eM_packed_dest, DxbcSrc::LU(bits[j]), DxbcSrc::LU(offset),
DxbcSrc::R(eM_temp).Select(j), eM_packed_src);
offset += bits[j];
}
}
}
void DxbcShaderTranslator::ExportToMemory() {
if (system_temp_memexport_written_ == UINT32_MAX) {
// No exports in the shader.
return;
}
// Allocate a register for temporary values at various stages.
uint32_t control_temp = PushSystemTemp();
// Safety check if the shared memory is bound as UAV.
system_constants_used_ |= 1ull << kSysConst_Flags_Index;
DxbcOpAnd(DxbcDest::R(control_temp, 0b0001),
DxbcSrc::CB(cbuffer_index_system_constants_,
uint32_t(CbufferRegister::kSystemConstants),
kSysConst_Flags_Vec)
.Select(kSysConst_Flags_Comp),
DxbcSrc::LU(kSysFlag_SharedMemoryIsUAV));
if (IsDxbcPixelShader()) {
// Disable memexport in pixel shaders with supersampling since VPOS is
// ambiguous.
if (edram_rov_used_) {
system_constants_used_ |= 1ull
<< kSysConst_EDRAMResolutionSquareScale_Index;
DxbcOpULT(DxbcDest::R(control_temp, 0b0010),
DxbcSrc::CB(cbuffer_index_system_constants_,
uint32_t(CbufferRegister::kSystemConstants),
kSysConst_EDRAMResolutionSquareScale_Vec)
.Select(kSysConst_EDRAMResolutionSquareScale_Comp),
DxbcSrc::LU(2));
DxbcOpAnd(DxbcDest::R(control_temp, 0b0001),
DxbcSrc::R(control_temp, DxbcSrc::kXXXX),
DxbcSrc::R(control_temp, DxbcSrc::kYYYY));
} else {
// Enough to check just Y because it's scaled for both 2x and 4x.
system_constants_used_ |= 1ull << kSysConst_SampleCountLog2_Index;
DxbcOpMovC(DxbcDest::R(control_temp, 0b0001),
DxbcSrc::CB(cbuffer_index_system_constants_,
uint32_t(CbufferRegister::kSystemConstants),
kSysConst_SampleCountLog2_Vec)
.Select(kSysConst_SampleCountLog2_Comp + 1),
DxbcSrc::LU(0), DxbcSrc::R(control_temp, DxbcSrc::kXXXX));
}
}
// Check if memexport can be done.
DxbcOpIf(true, DxbcSrc::R(control_temp, DxbcSrc::kXXXX));
// control_temp.x is now free.
for (uint32_t i = 0; i < kMaxMemExports; ++i) {
uint32_t eA_temp = system_temps_memexport_address_[i];
if (eA_temp == UINT32_MAX) {
// Export not used.
continue;
}
// For simplicity of access, gather actually used eM# registers for this
// export. Zero-initialize eM_offsets because excess elements of it may be
// accessed, for stable caching.
uint32_t eM_temps[5], eM_offsets[5] = {}, eM_count = 0;
for (uint32_t j = 0; j < 5; ++j) {
uint32_t eM_temp = system_temps_memexport_data_[i][j];
if (eM_temp == UINT32_MAX) {
continue;
}
eM_temps[eM_count] = eM_temp;
eM_offsets[eM_count] = j;
++eM_count;
}
if (eM_count == 0) {
continue;
}
// Swap red and blue if needed.
DxbcOpAnd(DxbcDest::R(control_temp, 0b0001),
DxbcSrc::R(eA_temp, DxbcSrc::kZZZZ),
DxbcSrc::LU(uint32_t(1) << 19));
for (uint32_t j = 0; j < eM_count; ++j) {
uint32_t eM_temp = eM_temps[j];
DxbcOpMovC(DxbcDest::R(eM_temp, 0b0101),
DxbcSrc::R(control_temp, DxbcSrc::kXXXX),
DxbcSrc::R(eM_temp, 0b000010), DxbcSrc::R(eM_temp));
}
// Initialize element size in control_temp.x to 4 bytes as this is the most
// common size.
DxbcDest element_size_dest(DxbcDest::R(control_temp, 0b0001));
DxbcSrc element_size_src(DxbcSrc::R(control_temp, DxbcSrc::kXXXX));
DxbcOpMov(element_size_dest, DxbcSrc::LU(4));
// Each eM should get a packed value in the destination format now.
// Extract format properties to control_temp.
// Y - signedness if fixed-point.
// Z - fractional/integer if fixed-point.
// W - color format.
DxbcOpUBFE(DxbcDest::R(control_temp, 0b1110), DxbcSrc::LU(0, 1, 1, 6),
DxbcSrc::LU(0, 16, 17, 8), DxbcSrc::R(eA_temp, DxbcSrc::kZZZZ));
DxbcSrc is_signed(DxbcSrc::R(control_temp, DxbcSrc::kYYYY));
DxbcSrc is_integer(DxbcSrc::R(control_temp, DxbcSrc::kZZZZ));
// Convert and pack the format.
DxbcOpSwitch(DxbcSrc::R(control_temp, DxbcSrc::kWWWW));
// control_temp.w is now free.
{
// k_8_8_8_8
// k_8_8_8_8_AS_16_16_16_16
DxbcOpCase(DxbcSrc::LU(uint32_t(ColorFormat::k_8_8_8_8)));
DxbcOpCase(DxbcSrc::LU(uint32_t(ColorFormat::k_8_8_8_8_AS_16_16_16_16)));
{
uint32_t bits[4] = {8, 8, 8, 8};
ExportToMemory_PackFixed32(eM_temps, eM_count, bits, is_integer,
is_signed);
}
DxbcOpBreak();
// k_2_10_10_10
// k_2_10_10_10_AS_16_16_16_16
DxbcOpCase(DxbcSrc::LU(uint32_t(ColorFormat::k_2_10_10_10)));
DxbcOpCase(
DxbcSrc::LU(uint32_t(ColorFormat::k_2_10_10_10_AS_16_16_16_16)));
{
uint32_t bits[4] = {10, 10, 10, 2};
ExportToMemory_PackFixed32(eM_temps, eM_count, bits, is_integer,
is_signed);
}
DxbcOpBreak();
// k_10_11_11
// k_10_11_11_AS_16_16_16_16
DxbcOpCase(DxbcSrc::LU(uint32_t(ColorFormat::k_10_11_11)));
DxbcOpCase(DxbcSrc::LU(uint32_t(ColorFormat::k_10_11_11_AS_16_16_16_16)));
{
uint32_t bits[4] = {11, 11, 10};
ExportToMemory_PackFixed32(eM_temps, eM_count, bits, is_integer,
is_signed);
}
DxbcOpBreak();
// k_11_11_10
// k_11_11_10_AS_16_16_16_16
DxbcOpCase(DxbcSrc::LU(uint32_t(ColorFormat::k_11_11_10)));
DxbcOpCase(DxbcSrc::LU(uint32_t(ColorFormat::k_11_11_10_AS_16_16_16_16)));
{
uint32_t bits[4] = {10, 11, 11};
ExportToMemory_PackFixed32(eM_temps, eM_count, bits, is_integer,
is_signed);
}
DxbcOpBreak();
// k_16_16
DxbcOpCase(DxbcSrc::LU(uint32_t(ColorFormat::k_16_16)));
{
uint32_t bits[4] = {16, 16};
ExportToMemory_PackFixed32(eM_temps, eM_count, bits, is_integer,
is_signed);
}
DxbcOpBreak();
// k_16_16_16_16
DxbcOpCase(DxbcSrc::LU(uint32_t(ColorFormat::k_16_16_16_16)));
DxbcOpMov(element_size_dest, DxbcSrc::LU(8));
DxbcOpIf(true, is_signed);
{
DxbcOpIf(false, is_integer);
for (uint32_t j = 0; j < eM_count; ++j) {
uint32_t eM_temp = eM_temps[j];
DxbcOpMul(DxbcDest::R(eM_temp), DxbcSrc::R(eM_temp),
DxbcSrc::LF(32767.0f));
}
DxbcOpEndIf();
for (uint32_t j = 0; j < eM_count; ++j) {
DxbcDest eM_dest(DxbcDest::R(eM_temps[j]));
DxbcSrc eM_src(DxbcSrc::R(eM_temps[j]));
DxbcOpMax(eM_dest, eM_src, DxbcSrc::LF(-32767.0f));
DxbcOpMin(eM_dest, eM_src, DxbcSrc::LF(32767.0f));
}
}
DxbcOpElse();
{
DxbcOpIf(false, is_integer);
for (uint32_t j = 0; j < eM_count; ++j) {
uint32_t eM_temp = eM_temps[j];
DxbcOpMul(DxbcDest::R(eM_temp), DxbcSrc::R(eM_temp),
DxbcSrc::LF(65535.0f));
}
DxbcOpEndIf();
for (uint32_t j = 0; j < eM_count; ++j) {
DxbcDest eM_dest(DxbcDest::R(eM_temps[j]));
DxbcSrc eM_src(DxbcSrc::R(eM_temps[j]));
DxbcOpMax(eM_dest, eM_src, DxbcSrc::LF(0.0f));
DxbcOpMin(eM_dest, eM_src, DxbcSrc::LF(65535.0f));
}
}
DxbcOpEndIf();
for (uint32_t j = 0; j < eM_count; ++j) {
uint32_t eM_temp = eM_temps[j];
// Round to the nearest integer, according to the rules of handling
// integer formats in Direct3D.
DxbcOpRoundNE(DxbcDest::R(eM_temp), DxbcSrc::R(eM_temp));
DxbcOpFToI(DxbcDest::R(eM_temp), DxbcSrc::R(eM_temp));
DxbcOpBFI(DxbcDest::R(eM_temp, 0b0011), DxbcSrc::LU(16),
DxbcSrc::LU(16), DxbcSrc::R(eM_temp, 0b1101),
DxbcSrc::R(eM_temp, 0b1000));
}
DxbcOpBreak();
// k_16_16_FLOAT
DxbcOpCase(DxbcSrc::LU(uint32_t(ColorFormat::k_16_16_FLOAT)));
for (uint32_t j = 0; j < eM_count; ++j) {
uint32_t eM_temp = eM_temps[j];
DxbcOpF32ToF16(DxbcDest::R(eM_temp, 0b0011), DxbcSrc::R(eM_temp));
DxbcOpBFI(DxbcDest::R(eM_temp, 0b0001), DxbcSrc::LU(16),
DxbcSrc::LU(16), DxbcSrc::R(eM_temp, DxbcSrc::kYYYY),
DxbcSrc::R(eM_temp, DxbcSrc::kXXXX));
}
DxbcOpBreak();
// k_16_16_16_16_FLOAT
DxbcOpCase(DxbcSrc::LU(uint32_t(ColorFormat::k_16_16_16_16_FLOAT)));
DxbcOpMov(element_size_dest, DxbcSrc::LU(8));
for (uint32_t j = 0; j < eM_count; ++j) {
uint32_t eM_temp = eM_temps[j];
DxbcOpF32ToF16(DxbcDest::R(eM_temp), DxbcSrc::R(eM_temp));
DxbcOpBFI(DxbcDest::R(eM_temp, 0b0011), DxbcSrc::LU(16),
DxbcSrc::LU(16), DxbcSrc::R(eM_temp, 0b1101),
DxbcSrc::R(eM_temp, 0b1000));
}
DxbcOpBreak();
// k_32_FLOAT
// Already in the destination format, 4 bytes per element already
// selected.
// k_32_32_FLOAT
DxbcOpCase(DxbcSrc::LU(uint32_t(ColorFormat::k_32_32_FLOAT)));
DxbcOpMov(element_size_dest, DxbcSrc::LU(8));
// Already in the destination format.
DxbcOpBreak();
// k_32_32_32_32_FLOAT
DxbcOpCase(DxbcSrc::LU(uint32_t(ColorFormat::k_32_32_32_32_FLOAT)));
DxbcOpMov(element_size_dest, DxbcSrc::LU(16));
// Already in the destination format.
DxbcOpBreak();
}
DxbcOpEndSwitch();
// control_temp.yz are now free.
// Do endian swap.
{
DxbcDest endian_dest(DxbcDest::R(control_temp, 0b0010));
DxbcSrc endian_src(DxbcSrc::R(control_temp, DxbcSrc::kYYYY));
// Extract endianness into control_temp.y.
DxbcOpAnd(endian_dest, DxbcSrc::R(eA_temp, DxbcSrc::kZZZZ),
DxbcSrc::LU(0b111));
// Change 8-in-64 and 8-in-128 to 8-in-32.
for (uint32_t j = 0; j < 2; ++j) {
DxbcOpIEq(
DxbcDest::R(control_temp, 0b0100), endian_src,
DxbcSrc::LU(uint32_t(j ? Endian128::k8in128 : Endian128::k8in64)));
for (uint32_t k = 0; k < eM_count; ++k) {
uint32_t eM_temp = eM_temps[k];
DxbcOpMovC(DxbcDest::R(eM_temp),
DxbcSrc::R(control_temp, DxbcSrc::kZZZZ),
DxbcSrc::R(eM_temp, j ? 0b00011011 : 0b10110001),
DxbcSrc::R(eM_temp));
}
DxbcOpMovC(endian_dest, DxbcSrc::R(control_temp, DxbcSrc::kZZZZ),
DxbcSrc::LU(uint32_t(Endian128::k8in32)), endian_src);
}
uint32_t swap_temp = PushSystemTemp();
DxbcDest swap_temp_dest(DxbcDest::R(swap_temp));
DxbcSrc swap_temp_src(DxbcSrc::R(swap_temp));
// 8-in-16 or one half of 8-in-32.
DxbcOpSwitch(endian_src);
DxbcOpCase(DxbcSrc::LU(uint32_t(Endian128::k8in16)));
DxbcOpCase(DxbcSrc::LU(uint32_t(Endian128::k8in32)));
for (uint32_t j = 0; j < eM_count; ++j) {
DxbcDest eM_dest(DxbcDest::R(eM_temps[j]));
DxbcSrc eM_src(DxbcSrc::R(eM_temps[j]));
// Temp = X0Z0.
DxbcOpAnd(swap_temp_dest, eM_src, DxbcSrc::LU(0x00FF00FF));
// eM = YZW0.
DxbcOpUShR(eM_dest, eM_src, DxbcSrc::LU(8));
// eM = Y0W0.
DxbcOpAnd(eM_dest, eM_src, DxbcSrc::LU(0x00FF00FF));
// eM = YXWZ.
DxbcOpUMAd(eM_dest, swap_temp_src, DxbcSrc::LU(256), eM_src);
}
DxbcOpBreak();
DxbcOpEndSwitch();
// 16-in-32 or another half of 8-in-32.
DxbcOpSwitch(endian_src);
DxbcOpCase(DxbcSrc::LU(uint32_t(Endian128::k8in32)));
DxbcOpCase(DxbcSrc::LU(uint32_t(Endian128::k16in32)));
for (uint32_t j = 0; j < eM_count; ++j) {
DxbcDest eM_dest(DxbcDest::R(eM_temps[j]));
DxbcSrc eM_src(DxbcSrc::R(eM_temps[j]));
// Temp = ZW00.
DxbcOpUShR(swap_temp_dest, eM_src, DxbcSrc::LU(16));
// eM = ZWXY.
DxbcOpBFI(eM_dest, DxbcSrc::LU(16), DxbcSrc::LU(16), eM_src,
swap_temp_src);
}
DxbcOpBreak();
DxbcOpEndSwitch();
// Release swap_temp.
PopSystemTemp();
}
// control_temp.yz are now free.
DxbcDest address_dest(DxbcDest::R(eA_temp, 0b0001));
DxbcSrc address_src(DxbcSrc::R(eA_temp, DxbcSrc::kXXXX));
// Multiply the base address by dword size, also dropping the 0x40000000
// bit.
DxbcOpIShL(address_dest, address_src, DxbcSrc::LU(2));
// Drop the exponent in the element index.
DxbcOpAnd(DxbcDest::R(eA_temp, 0b0010), DxbcSrc::R(eA_temp, DxbcSrc::kYYYY),
DxbcSrc::LU((1 << 23) - 1));
// Add the offset of the first written element to the base address.
DxbcOpUMAd(address_dest, DxbcSrc::R(eA_temp, DxbcSrc::kYYYY),
element_size_src, address_src);
// Do the writes.
DxbcSrc eM_written_src(
DxbcSrc::R(system_temp_memexport_written_).Select(i >> 2));
uint32_t eM_written_base = 1u << ((i & 3) << 3);
for (uint32_t j = 0; j < eM_count; ++j) {
// Go to the next eM#.
uint32_t eM_relative_offset = eM_offsets[j] - (j ? eM_offsets[j - 1] : 0);
if (eM_relative_offset) {
if (eM_relative_offset == 1) {
DxbcOpIAdd(address_dest, element_size_src, address_src);
} else {
DxbcOpUMAd(address_dest, DxbcSrc::LU(eM_relative_offset),
element_size_src, address_src);
}
}
// Check if the eM# was actually written to on the execution path.
DxbcOpAnd(DxbcDest::R(control_temp, 0b0010), eM_written_src,
DxbcSrc::LU(eM_written_base << eM_offsets[j]));
DxbcOpIf(true, DxbcSrc::R(control_temp, DxbcSrc::kYYYY));
// Write the element of the needed size.
DxbcSrc eM_src(DxbcSrc::R(eM_temps[j]));
DxbcOpSwitch(element_size_src);
for (uint32_t k = 1; k <= 4; k <<= 1) {
DxbcOpCase(DxbcSrc::LU(k * 4));
DxbcOpStoreRaw(
DxbcDest::U(0, uint32_t(UAVRegister::kSharedMemory), (1 << k) - 1),
address_src, eM_src);
DxbcOpBreak();
}
DxbcOpEndSwitch();
DxbcOpEndIf();
}
// control_temp.y is now free.
}
// Close the memexport possibility check.
DxbcOpEndIf();
// Release control_temp.
PopSystemTemp();
}
} // namespace gpu
} // namespace xe