/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2022 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #ifndef XENIA_GPU_DRAW_UTIL_H_ #define XENIA_GPU_DRAW_UTIL_H_ #include #include #include #include "xenia/base/assert.h" #include "xenia/gpu/register_file.h" #include "xenia/gpu/shader.h" #include "xenia/gpu/trace_writer.h" #include "xenia/gpu/xenos.h" #include "xenia/memory.h" namespace xe { namespace gpu { namespace draw_util { constexpr bool IsPrimitiveLine(bool vgt_output_path_is_tessellation_enable, xenos::PrimitiveType type) { if (vgt_output_path_is_tessellation_enable && type == xenos::PrimitiveType::kLinePatch) { // For patch primitive types, the major mode is always explicit, so just // checking if VGT_OUTPUT_PATH_CNTL::path_select is kTessellationEnable is // enough. return true; } switch (type) { case xenos::PrimitiveType::kLineList: case xenos::PrimitiveType::kLineStrip: case xenos::PrimitiveType::kLineLoop: case xenos::PrimitiveType::k2DLineStrip: return true; default: break; } return false; } inline bool IsPrimitiveLine(const RegisterFile& regs) { return IsPrimitiveLine(regs.Get().path_select == xenos::VGTOutputPath::kTessellationEnable, regs.Get().prim_type); } constexpr uint32_t EncodeIsPrimitivePolygonalTable() { unsigned result = 0; #define TRUEFOR(x) \ result |= 1U << static_cast(xenos::PrimitiveType::x) TRUEFOR(kTriangleList); TRUEFOR(kTriangleFan); TRUEFOR(kTriangleStrip); TRUEFOR(kTriangleWithWFlags); TRUEFOR(kQuadList); TRUEFOR(kQuadStrip); TRUEFOR(kPolygon); #undef TRUEFOR // TODO(Triang3l): Investigate how kRectangleList should be treated - possibly // actually drawn as two polygons on the console, however, the current // geometry shader doesn't care about the winding order - allowing backface // culling for rectangles currently breaks 4D53082D. return result; } // Polygonal primitive types (not including points and lines) are rasterized as // triangles, have front and back faces, and also support face culling and fill // modes (polymode_front_ptype, polymode_back_ptype). Other primitive types are // always "front" (but don't support front face and back face culling, according // to OpenGL and Vulkan specifications - even if glCullFace is // GL_FRONT_AND_BACK, points and lines are still drawn), and may in some cases // use the "para" registers instead of "front" or "back" (for "parallelogram" - // like poly_offset_para_enable). XE_FORCEINLINE constexpr bool IsPrimitivePolygonal(bool vgt_output_path_is_tessellation_enable, xenos::PrimitiveType type) { if (vgt_output_path_is_tessellation_enable && (type == xenos::PrimitiveType::kTrianglePatch || type == xenos::PrimitiveType::kQuadPatch)) { // For patch primitive types, the major mode is always explicit, so just // checking if VGT_OUTPUT_PATH_CNTL::path_select is kTessellationEnable is // enough. return true; } // chrispy: expensive jumptable, use bit table instead constexpr uint32_t primitive_polygonal_table = EncodeIsPrimitivePolygonalTable(); return (primitive_polygonal_table & (1U << static_cast(type))) != 0; } XE_FORCEINLINE bool IsPrimitivePolygonal(const RegisterFile& regs) { return IsPrimitivePolygonal( regs.Get().path_select == xenos::VGTOutputPath::kTessellationEnable, regs.Get().prim_type); } // Whether with the current state, any samples to rasterize (for any reason, not // only to write something to a render target, but also to do sample counting or // pixel shader memexport) can be generated. Finally dropping draw calls can // only be done if the vertex shader doesn't memexport. Checks mostly special // cases (for both the guest and usual host implementations), not everything // like whether viewport / scissor are empty (until this truly matters in any // game, of course). bool IsRasterizationPotentiallyDone(const RegisterFile& regs, bool primitive_polygonal); // Direct3D 10.1+ standard sample positions, also used in Vulkan, for // calculations related to host MSAA, in 1/16th of a pixel. extern const int8_t kD3D10StandardSamplePositions2x[2][2]; extern const int8_t kD3D10StandardSamplePositions4x[4][2]; reg::RB_DEPTHCONTROL GetNormalizedDepthControl(const RegisterFile& regs); // Direct3D 9 and Xenos constant polygon offset is an absolute floating-point // value. // It's possibly treated just as an absolute offset by the Xenos too - the // PA_SU_POLY_OFFSET_DB_FMT_CNTL::POLY_OFFSET_DB_IS_FLOAT_FMT switch was added // later in the R6xx, though this needs verification. // 5454082B, for example, for float24, sets the bias to 0.000002 - or slightly // above 2^-19. // The total polygon offset formula specified by Direct3D 9 is: // `offset = slope * slope factor + constant offset` // // Direct3D 10, Metal, OpenGL and Vulkan, however, take the constant polygon // offset factor as a relative value, with the formula being: // `offset = slope * slope factor + // maximum resolvable difference * constant factor` // where the maximum resolvable difference is: // - For a fixed-point depth buffer, the minimum representable non-zero value in // the depth buffer, that is 1 / (2^24 - 1) for unorm24 (on Vulkan though it's // allowed to be up to 2 / 2^24 in this case). // - For a floating-point depth buffer, it's: // 2 ^ (exponent of the maximum Z in the primitive - the number of explicitly // stored mantissa bits) // (23 explicitly stored bits for float32 - and 20 explicitly stored bits for // float24). // // While the polygon offset is a fixed-function feature in the pipeline, and the // formula can't be toggled between absolute and relative, it's important that // Xenia translates the guest absolute depth bias into the host relative depth // bias in a way that the values that separate coplanar geometry on the guest // qualitatively also still correctly separate them on the host. // // It also should be taken into account that on Xenia, float32 depth values may // be snapped to float24 directly in the translated pixel shaders (to prevent // data loss if after reuploading a depth buffer to the EDRAM there's no way to // recover the full-precision value, that results in the inability to perform // more rendering passes for the same geometry), and not only to the nearest // value, but also just truncating them (so in case of data loss, the "greater // or equal" depth test function still works). // // Because of this, the depth bias may be lost if Xenia translates it into a too // small value, and the conversion of the depth is done in the pixel shader. // Specifically, Xenia should not simply convert a value that separates coplanar // primitives as float24 just into something that still separates them as // float32. Essentially, if conversion to float24 is done in the pixel shader, // Xenia should make sure the polygon offset on the host is calculated as if the // host had float24 depth too, not float32. // Applies to both native host unorm24, and unorm24 emulated as host float32. // For native unorm24, this is exactly the inverse of the minimum representable // non-zero value. // For unorm24 emulated as float32, the minimum representable non-zero value for // a primitive in the [0.5, 1) range (the worst case that forward depth reaches // very quickly, at nearly `2 * near clipping plane distance`) is 2 ^ (-1 - 23), // or 2^-24, and this factor is almost 2^24. constexpr float kD3D10PolygonOffsetFactorUnorm24 = float((UINT32_C(1) << 24) - 1); // For a host floating-point depth buffer, the integer value of the depth bias // is roughly how many ULPs primitives should be separated by. // // Float24, however, has 3 mantissa bits fewer than float32 - so one float24 ULP // corresponds to 8 float32 ULPs - which means each conceptual "layer" of the // guest value should correspond to a polygon offset of 8. So, after the guest // absolute value is converted to "layers", it should be multiplied by 8 before // being used on the host with a float32 depth buffer. // // The scale for converting the guest absolute depth bias to the "layers" needs // to be determined for the worst case - specifically, the [0.5, 1) range (1 is // a single value, so there's no need to take it into consideration). In this // range, Z values have the exponent of -1. Therefore, for float24, the absolute // offset is obtained from the "layer index" in this range as (disregarding the // slope term): // offset = 2 ^ (-1 - 20) * constant factor // Thus, to obtain the constant factor from the absolute offset in the range // with the lowest absolute precision, the offset needs to be multiplied by // 2^21. // // Finally, the 0...0.5 range may be used on the host to represent the 0...1 // guest depth range to be able to copy all possible encodings, which are // [0, 2), via a [0, 1] depth output variable, during EDRAM contents // reinterpretation. This is done by scaling the viewport depth bounds by 0.5. // However, there's no need to do anything to handle this scenario in the // polygon offset - it's calculated after applying the viewport transformation, // and the maximum Z value in the primitive will have an exponent lowered by 1, // thus the result will also have an exponent lowered by 1 - exactly what's // needed for remapping 0...1 to 0...0.5. constexpr float kD3D10PolygonOffsetFactorFloat24 = float(UINT32_C(1) << (21 + 3)); inline int32_t GetD3D10IntegerPolygonOffset( xenos::DepthRenderTargetFormat depth_format, float polygon_offset) { bool is_float24 = depth_format == xenos::DepthRenderTargetFormat::kD24FS8; // Using `ceil` because more offset is better, especially if flooring would // result in 0 - conceptually, if the offset is used at all, primitives need // to be separated in the depth buffer. int32_t polygon_offset_int = int32_t( std::ceil(std::abs(polygon_offset) * (is_float24 ? kD3D10PolygonOffsetFactorFloat24 * (1.0f / 8.0f) : kD3D10PolygonOffsetFactorUnorm24))); // For float24, the conversion may be done in the translated pixel shaders, // including via truncation rather than rounding to the nearest. So, making // the integer bias always in the increments of 2^3 (2 ^ the difference in the // mantissa bit count between float32 and float24), and because of that, doing // `ceil` before changing the units from float24 ULPs to float32 ULPs. if (is_float24) { polygon_offset_int <<= 3; } return polygon_offset < 0 ? -polygon_offset_int : polygon_offset_int; } // For hosts not supporting separate front and back polygon offsets, returns the // polygon offset for the face which likely needs the offset the most (and that // will not be culled). The values returned will have the units of the original // registers (the scale is for 1/16 subpixels, multiply by // xenos::kPolygonOffsetScaleSubpixelUnit outside if the value for pixels is // needed). void GetPreferredFacePolygonOffset(const RegisterFile& regs, bool primitive_polygonal, float& scale_out, float& offset_out); inline bool DoesCoverageDependOnAlpha(reg::RB_COLORCONTROL rb_colorcontrol) { return (rb_colorcontrol.alpha_test_enable && rb_colorcontrol.alpha_func != xenos::CompareFunction::kAlways) || rb_colorcontrol.alpha_to_mask_enable; } // Whether the pixel shader can be disabled on the host to speed up depth // pre-passes and shadowmaps. The shader must have its ucode analyzed. If // IsRasterizationPotentiallyDone, this shouldn't be called, and assumed false // instead. Helps reject the pixel shader in some cases - memexport draws in // 4D5307E6, and also most of some 1-point draws not covering anything done for // some reason in different games with a leftover pixel shader from the previous // draw, but with SQ_PROGRAM_CNTL destroyed, reducing the number of // unpredictable unneeded translations of random shaders with different host // modification bits, such as register count and depth format-related (though // shaders with side effects on depth or memory export will still be preserved). bool IsPixelShaderNeededWithRasterization(const Shader& shader, const RegisterFile& regs); struct ViewportInfo { // Offset from render target UV = 0 to +UV. // For simplicity of cropping to the maximum size on the host; to match the // Direct3D 12 clipping / scissoring behavior with a fractional viewport, to // floor(TopLeftXY) ... floor(TopLeftXY + WidthHeight), on the real AMD, Intel // and Nvidia hardware (not WARP); as well as to hide the differences between // 0 and 8+ viewportSubPixelBits on Vulkan, and to prevent any numerical error // in bound checking in host APIs, viewport bounds are returned as integers. // Also they're returned as non-negative, also to make it easier to crop (so // Vulkan maxViewportDimensions and viewportBoundsRange don't have to be // handled separately - maxViewportDimensions is greater than or equal to the // largest framebuffer image size, so it's safe, and viewportBoundsRange is // always bigger than maxViewportDimensions. All fractional offsetting, // including the half-pixel offset, and cropping are handled via ndc_scale and // ndc_offset. uint32_t xy_offset[2]; // Extent can be zero for an empty viewport - host APIs not supporting empty // viewports need to use an empty scissor rectangle. uint32_t xy_extent[2]; float z_min; float z_max; // The scale is applied before the offset (like using multiply-add). float ndc_scale[3]; float ndc_offset[3]; }; static_assert(sizeof(xenos::DepthRenderTargetFormat) == sizeof(uint32_t), "Change in depthrendertargetformat throws off " "getviewportinfoargs by a bit"); struct GetViewportInfoArgs { union alignas(64) { struct { // group 1 uint32_t x_max; uint32_t y_max; union { struct { uint32_t origin_bottom_left : 1; uint32_t allow_reverse_z : 1; uint32_t convert_z_to_float24 : 1; uint32_t full_float24_in_0_to_1 : 1; uint32_t pixel_shader_writes_depth : 1; xenos::DepthRenderTargetFormat depth_format : 1; }; uint32_t packed_portions; }; reg::RB_DEPTHCONTROL normalized_depth_control; // group 2 reg::PA_CL_CLIP_CNTL pa_cl_clip_cntl; reg::PA_CL_VTE_CNTL pa_cl_vte_cntl; reg::PA_SU_SC_MODE_CNTL pa_su_sc_mode_cntl; reg::PA_SU_VTX_CNTL pa_su_vtx_cntl; // group 3 reg::PA_SC_WINDOW_OFFSET pa_sc_window_offset; float PA_CL_VPORT_XSCALE; float PA_CL_VPORT_YSCALE; float PA_CL_VPORT_ZSCALE; float PA_CL_VPORT_XOFFSET; float PA_CL_VPORT_YOFFSET; float PA_CL_VPORT_ZOFFSET; uint32_t padding_set_to_0; }; #if XE_ARCH_AMD64 == 1 struct { __m128i first4; // x_max, y_max, packed_portions, // normalized_depth_control __m128i second4; // pa_cl_clip_cntl, pa_cl_vte_cntl, pa_su_sc_mode_cntl, // pa_su_vtx_cntl __m128i third4; // pa_sc_window_offset, PA_CL_VPORT_XSCALE, // PA_CL_VPORT_YSCALE, PA_CL_VPORT_ZSCALE __m128i last4; // PA_CL_VPORT_XOFFSET, PA_CL_VPORT_YOFFSET, // PA_CL_VPORT_ZOFFSET, padding_set_to_0 }; #endif }; // everything that follows here does not need to be compared uint32_t draw_resolution_scale_x; uint32_t draw_resolution_scale_y; divisors::MagicDiv draw_resolution_scale_x_divisor; divisors::MagicDiv draw_resolution_scale_y_divisor; void Setup(uint32_t _draw_resolution_scale_x, uint32_t _draw_resolution_scale_y, divisors::MagicDiv _draw_resolution_scale_x_divisor, divisors::MagicDiv _draw_resolution_scale_y_divisor, bool _origin_bottom_left, uint32_t _x_max, uint32_t _y_max, bool _allow_reverse_z, reg::RB_DEPTHCONTROL _normalized_depth_control, bool _convert_z_to_float24, bool _full_float24_in_0_to_1, bool _pixel_shader_writes_depth) { packed_portions = 0; padding_set_to_0 = 0; // important to zero this draw_resolution_scale_x = _draw_resolution_scale_x; draw_resolution_scale_y = _draw_resolution_scale_y; draw_resolution_scale_x_divisor = _draw_resolution_scale_x_divisor; draw_resolution_scale_y_divisor = _draw_resolution_scale_y_divisor; origin_bottom_left = _origin_bottom_left; x_max = _x_max; y_max = _y_max; allow_reverse_z = _allow_reverse_z; normalized_depth_control = _normalized_depth_control; convert_z_to_float24 = _convert_z_to_float24; full_float24_in_0_to_1 = _full_float24_in_0_to_1; pixel_shader_writes_depth = _pixel_shader_writes_depth; } void SetupRegisterValues(const RegisterFile& regs) { pa_cl_clip_cntl = regs.Get(); pa_cl_vte_cntl = regs.Get(); pa_su_sc_mode_cntl = regs.Get(); pa_su_vtx_cntl = regs.Get(); PA_CL_VPORT_XSCALE = regs[XE_GPU_REG_PA_CL_VPORT_XSCALE].f32; PA_CL_VPORT_YSCALE = regs[XE_GPU_REG_PA_CL_VPORT_YSCALE].f32; PA_CL_VPORT_ZSCALE = regs[XE_GPU_REG_PA_CL_VPORT_ZSCALE].f32; PA_CL_VPORT_XOFFSET = regs[XE_GPU_REG_PA_CL_VPORT_XOFFSET].f32; PA_CL_VPORT_YOFFSET = regs[XE_GPU_REG_PA_CL_VPORT_YOFFSET].f32; PA_CL_VPORT_ZOFFSET = regs[XE_GPU_REG_PA_CL_VPORT_ZOFFSET].f32; pa_sc_window_offset = regs.Get(); depth_format = regs.Get().depth_format; } XE_FORCEINLINE bool operator==(const GetViewportInfoArgs& prev) { #if XE_ARCH_AMD64 == 0 bool result = true; auto accum_eq = [&result](auto x, auto y) { result &= (x == y); }; #define EQC(field) accum_eq(field, prev.field) EQC(x_max); EQC(y_max); EQC(packed_portions); EQC(normalized_depth_control.value); EQC(pa_cl_clip_cntl.value); EQC(pa_cl_vte_cntl.value); EQC(pa_su_sc_mode_cntl.value); EQC(pa_su_vtx_cntl.value); EQC(PA_CL_VPORT_XSCALE); EQC(PA_CL_VPORT_YSCALE); EQC(PA_CL_VPORT_ZSCALE); EQC(PA_CL_VPORT_XOFFSET); EQC(PA_CL_VPORT_YOFFSET); EQC(PA_CL_VPORT_ZOFFSET); EQC(pa_sc_window_offset.value); #undef EQC return result; #else __m128i mask1 = _mm_cmpeq_epi32(first4, prev.first4); __m128i mask2 = _mm_cmpeq_epi32(second4, prev.second4); __m128i mask3 = _mm_cmpeq_epi32(third4, prev.third4); __m128i unified1 = _mm_and_si128(mask1, mask2); __m128i mask4 = _mm_cmpeq_epi32(last4, prev.last4); __m128i unified2 = _mm_and_si128(unified1, mask3); __m128i unified3 = _mm_and_si128(unified2, mask4); return _mm_movemask_epi8(unified3) == 0xFFFF; #endif } }; // Converts the guest viewport (or fakes one if drawing without a viewport) to // a viewport, plus values to multiply-add the returned position by, usable on // host graphics APIs such as Direct3D 11+ and Vulkan, also forcing it to the // Direct3D clip space with 0...W Z rather than -W...W. void GetHostViewportInfo(GetViewportInfoArgs* XE_RESTRICT args, ViewportInfo& viewport_info_out); struct Scissor { // Offset from render target UV = 0 to +UV. uint32_t offset[2]; // Extent can be zero. uint32_t extent[2]; }; void GetScissor(const RegisterFile& regs, Scissor& scissor_out, bool clamp_to_surface_pitch = true); // Returns the color component write mask for the draw command taking into // account which color targets are written to by the pixel shader, as well as // components that don't exist in the formats of the render targets (render // targets with only non-existent components written are skipped, but // non-existent components are forced to written if some existing components of // the render target are actually used to make sure the host driver doesn't try // to take a slow path involving reading and mixing if there are any disabled // components even if they don't actually exist). uint32_t GetNormalizedColorMask(const RegisterFile& regs, uint32_t pixel_shader_writes_color_targets); // Never an identity conversion - can always write conditional move instructions // to shaders that will be no-ops for conversion from guest to host samples. // While we don't know the exact guest sample pattern, due to the way // multisampled render targets are stored in the memory (like 1x2 single-sampled // pixels with 2x MSAA, or like 2x2 single-sampled pixels with 4x), assuming // that the sample 0 is the top sample, and the sample 1 is the bottom one. inline uint32_t GetD3D10SampleIndexForGuest2xMSAA( uint32_t guest_sample_index, bool native_2x_msaa_supported) { assert(guest_sample_index <= 1); if (native_2x_msaa_supported) { // On Direct3D 10.1 with native 2x MSAA, the top-left sample is 1, and the // bottom-right sample is 0. return guest_sample_index ? 0 : 1; } // When native 2x MSAA is not supported, using the top-left (0) and the // bottom-right (3) samples of the guaranteed 4x MSAA. return guest_sample_index ? 3 : 0; } // To avoid passing values that the shader won't understand (even though // Direct3D 9 shouldn't pass them anyway). XE_NOINLINE XE_NOALIAS xenos::CopySampleSelect SanitizeCopySampleSelect( xenos::CopySampleSelect copy_sample_select, xenos::MsaaSamples msaa_samples, bool is_depth); // Packed structures are small and can be passed to the shaders in root/push // constants. union ResolveEdramInfo { uint32_t packed; struct { // With 32bpp/64bpp taken into account. uint32_t pitch_tiles : xenos::kEdramPitchTilesBits; xenos::MsaaSamples msaa_samples : xenos::kMsaaSamplesBits; uint32_t is_depth : 1; // With offset to the region that edram_offset_x/y_div_8 are relative to. uint32_t base_tiles : xenos::kEdramBaseTilesBits; uint32_t format : xenos::kRenderTargetFormatBits; uint32_t format_is_64bpp : 1; // Whether to fill the half-pixel offset gap on the left and the top sides // of the resolve region with the contents of the first surely covered // column / row with resolution scaling. uint32_t fill_half_pixel_offset : 1; }; ResolveEdramInfo() : packed(0) { static_assert_size(*this, sizeof(packed)); } }; union ResolveCoordinateInfo { uint32_t packed; struct { // In pixels relatively to the origin of the EDRAM base tile. // 0...9 for 0...72. uint32_t edram_offset_x_div_8 : 4; // 0...1 for 0...8. uint32_t edram_offset_y_div_8 : 1; // In pixels. // May be zero if the original rectangle was somehow specified in a // totally broken way - in this case, the resolve must be dropped. uint32_t width_div_8 : xenos::kResolveSizeBits - xenos::kResolveAlignmentPixelsLog2; // 1 to 7. uint32_t draw_resolution_scale_x : 3; uint32_t draw_resolution_scale_y : 3; }; ResolveCoordinateInfo() : packed(0) { static_assert_size(*this, sizeof(packed)); } }; // Returns tiles actually covered by a resolve area. Row length used is width of // the area in tiles, but the pitch between rows is edram_info.pitch_tiles. void GetResolveEdramTileSpan(ResolveEdramInfo edram_info, ResolveCoordinateInfo coordinate_info, uint32_t height_div_8, uint32_t& base_out, uint32_t& row_length_used_out, uint32_t& rows_out); union ResolveCopyDestCoordinateInfo { uint32_t packed; struct { // 0...16384/32. uint32_t pitch_aligned_div_32 : xenos::kTexture2DCubeMaxWidthHeightLog2 + 2 - xenos::kTextureTileWidthHeightLog2; uint32_t height_aligned_div_32 : xenos::kTexture2DCubeMaxWidthHeightLog2 + 2 - xenos::kTextureTileWidthHeightLog2; // Up to the maximum period of the texture tiled address function (128x128 // for 2D 1bpb). uint32_t offset_x_div_8 : 7 - xenos::kResolveAlignmentPixelsLog2; uint32_t offset_y_div_8 : 7 - xenos::kResolveAlignmentPixelsLog2; xenos::CopySampleSelect copy_sample_select : 3; }; ResolveCopyDestCoordinateInfo() : packed(0) { static_assert_size(*this, sizeof(packed)); } }; // For backends with Shader Model 5-like compute, host shaders to use to perform // copying in resolve operations. enum class ResolveCopyShaderIndex { kFast32bpp1x2xMSAA, kFast32bpp4xMSAA, kFast64bpp1x2xMSAA, kFast64bpp4xMSAA, kFull8bpp, kFull16bpp, kFull32bpp, kFull64bpp, kFull128bpp, kCount, kUnknown = kCount, }; struct ResolveCopyShaderInfo { // Debug name of the pipeline state object with this shader. const char* debug_name; // Whether the EDRAM source needs be bound as a raw buffer (ByteAddressBuffer // in Direct3D) since it can load different numbers of 32-bit values at once // on some hardware. If the host API doesn't support raw buffers, a typed // buffer with source_bpe_log2-byte elements needs to be used instead. bool source_is_raw; // Log2 of bytes per element of the type of the EDRAM buffer bound to the // shader (at least 2). uint32_t source_bpe_log2; // Log2 of bytes per element of the type of the destination buffer bound to // the shader (at least 2 because of the 128 megatexel minimum requirement on // Direct3D 10+ - D3D12_REQ_BUFFER_RESOURCE_TEXEL_COUNT_2_TO_EXP - that // prevents binding the entire shared memory buffer with smaller element // sizes). uint32_t dest_bpe_log2; // Log2 of number of pixels in a single thread group along X and Y. 64 threads // per group preferred (GCN lane count). uint32_t group_size_x_log2, group_size_y_log2; }; extern const ResolveCopyShaderInfo resolve_copy_shader_info[size_t(ResolveCopyShaderIndex::kCount)]; struct ResolveCopyShaderConstants { // When the destination base is not needed (not binding the entire shared // memory buffer - with resoluion scaling, for instance), only the // DestRelative part may be passed to the shader to use less constants. struct DestRelative { ResolveEdramInfo edram_info; ResolveCoordinateInfo coordinate_info; reg::RB_COPY_DEST_INFO dest_info; ResolveCopyDestCoordinateInfo dest_coordinate_info; }; DestRelative dest_relative; uint32_t dest_base; }; struct ResolveClearShaderConstants { // rt_specific is different for color and depth, the rest is the same and can // be preserved in the root bindings when going from depth to color. struct RenderTargetSpecific { uint32_t clear_value[2]; ResolveEdramInfo edram_info; }; RenderTargetSpecific rt_specific; ResolveCoordinateInfo coordinate_info; }; struct ResolveInfo { reg::RB_COPY_CONTROL rb_copy_control; // depth_edram_info / depth_original_base and color_edram_info / // color_original_base are set up if copying or clearing color and depth // respectively, according to RB_COPY_CONTROL. ResolveEdramInfo depth_edram_info; ResolveEdramInfo color_edram_info; // Original bases, without adjustment to a 160x32 region for packed offsets, // for locating host render targets to perform clears if host render targets // are used for EDRAM emulation - the same as the base that the render target // will likely be used for drawing next, to prevent unneeded tile ownership // transfers between clears and first usage if clearing a subregion. uint32_t depth_original_base; uint32_t color_original_base; ResolveCoordinateInfo coordinate_info; // Like coordinate_info.width_div_8, but not needed for shaders. // In pixels. // May be zero if the original rectangle was somehow specified in a totally // broken way - in this case, the resolve must be dropped. uint32_t height_div_8; reg::RB_COPY_DEST_INFO copy_dest_info; ResolveCopyDestCoordinateInfo copy_dest_coordinate_info; // The address of the texture or the location within the texture that // copy_dest_coordinate_info.offset_x/y_div_8 - the origin of the copy // destination - is relative to. uint32_t copy_dest_base; // Memory range that will potentially be modified by copying to the texture. // copy_dest_extent_length may be zero if something is wrong with the // destination, in this case, clearing may still be done, but copying must be // dropped. uint32_t copy_dest_extent_start; uint32_t copy_dest_extent_length; // The clear shaders always write to a uint4 view of EDRAM. uint32_t rb_depth_clear; uint32_t rb_color_clear; uint32_t rb_color_clear_lo; bool IsCopyingDepth() const { return rb_copy_control.copy_src_select >= xenos::kMaxColorRenderTargets; } // See GetResolveEdramTileSpan documentation for explanation. void GetCopyEdramTileSpan(uint32_t& base_out, uint32_t& row_length_used_out, uint32_t& rows_out, uint32_t& pitch_out) const { ResolveEdramInfo edram_info = IsCopyingDepth() ? depth_edram_info : color_edram_info; GetResolveEdramTileSpan(edram_info, coordinate_info, height_div_8, base_out, row_length_used_out, rows_out); pitch_out = edram_info.pitch_tiles; } ResolveCopyShaderIndex GetCopyShader( uint32_t draw_resolution_scale_x, uint32_t draw_resolution_scale_y, ResolveCopyShaderConstants& constants_out, uint32_t& group_count_x_out, uint32_t& group_count_y_out) const; bool IsClearingDepth() const { return rb_copy_control.depth_clear_enable != 0; } bool IsClearingColor() const { return !IsCopyingDepth() && rb_copy_control.color_clear_enable != 0; } void GetDepthClearShaderConstants( ResolveClearShaderConstants& constants_out) const { assert_true(IsClearingDepth()); constants_out.rt_specific.clear_value[0] = rb_depth_clear; constants_out.rt_specific.clear_value[1] = rb_depth_clear; constants_out.rt_specific.edram_info = depth_edram_info; constants_out.coordinate_info = coordinate_info; } void GetColorClearShaderConstants( ResolveClearShaderConstants& constants_out) const { assert_true(IsClearingColor()); // Not doing -32...32 to -1...1 clamping here as a hack for k_16_16 and // k_16_16_16_16 blending emulation when using host render targets as it // would be inconsistent with the usual way of clearing with a depth quad. // TODO(Triang3l): Check which 32-bit portion is in which register. constants_out.rt_specific.clear_value[0] = rb_color_clear; constants_out.rt_specific.clear_value[1] = rb_color_clear_lo; constants_out.rt_specific.edram_info = color_edram_info; constants_out.coordinate_info = coordinate_info; } std::pair GetClearShaderGroupCount( uint32_t draw_resolution_scale_x, uint32_t draw_resolution_scale_y) const { // 8 guest MSAA samples per invocation. uint32_t width_samples_div_8 = coordinate_info.width_div_8; uint32_t height_samples_div_8 = height_div_8; xenos::MsaaSamples samples = IsCopyingDepth() ? depth_edram_info.msaa_samples : color_edram_info.msaa_samples; if (samples >= xenos::MsaaSamples::k2X) { height_samples_div_8 <<= 1; if (samples >= xenos::MsaaSamples::k4X) { width_samples_div_8 <<= 1; } } width_samples_div_8 *= draw_resolution_scale_x; height_samples_div_8 *= draw_resolution_scale_y; return std::make_pair((width_samples_div_8 + uint32_t(7)) >> 3, height_samples_div_8); } }; // Returns false if there was an error obtaining the info making it totally // invalid. fixed_rg[ba]16_truncated_to_minus_1_to_1 is false if 16_16[_16_16] // color render target formats are properly emulated as -32...32, true if // emulated as snorm, with range limited to -1...1, but with correct blending // within that range. bool GetResolveInfo(const RegisterFile& regs, const Memory& memory, TraceWriter& trace_writer, uint32_t draw_resolution_scale_x, uint32_t draw_resolution_scale_y, bool fixed_rg16_truncated_to_minus_1_to_1, bool fixed_rgba16_truncated_to_minus_1_to_1, ResolveInfo& info_out); } // namespace draw_util } // namespace gpu } // namespace xe #endif // XENIA_GPU_DRAW_UTIL_H_