/** ****************************************************************************** * 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