diff --git a/.gitmodules b/.gitmodules index 8c0cf44fb..4d88ce646 100644 --- a/.gitmodules +++ b/.gitmodules @@ -108,3 +108,6 @@ [submodule "third_party/miniaudio"] path = third_party/miniaudio url = https://github.com/mackron/miniaudio.git +[submodule "third_party/DirectX-Headers"] + path = third_party/DirectX-Headers + url = https://github.com/microsoft/DirectX-Headers.git diff --git a/src/xenia/gpu/d3d12/d3d12_command_processor.cc b/src/xenia/gpu/d3d12/d3d12_command_processor.cc index 884b76afe..ce6d0eea0 100644 --- a/src/xenia/gpu/d3d12/d3d12_command_processor.cc +++ b/src/xenia/gpu/d3d12/d3d12_command_processor.cc @@ -2877,7 +2877,6 @@ bool D3D12CommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type, if (host_render_targets_used) { bound_depth_and_color_render_target_bits = render_target_cache_->GetLastUpdateBoundRenderTargets( - render_target_cache_->gamma_render_target_as_srgb(), bound_depth_and_color_render_target_formats); } else { bound_depth_and_color_render_target_bits = 0; @@ -3238,8 +3237,7 @@ bool D3D12CommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type, // Invalidate textures in memexported memory and watch for changes. for (const draw_util::MemExportRange& memexport_range : memexport_ranges_) { shared_memory_->RangeWrittenByGpu( - memexport_range.base_address_dwords << 2, memexport_range.size_bytes, - false); + memexport_range.base_address_dwords << 2, memexport_range.size_bytes); } if (GetGPUSetting(GPUSetting::ReadbackMemexport)) { // Read the exported data on the CPU. @@ -4433,7 +4431,8 @@ XE_NOINLINE void D3D12CommandProcessor::UpdateSystemConstantValues_Impl( flags |= uint32_t(alpha_test_function) << DxbcShaderTranslator::kSysFlag_AlphaPassIfLess_Shift; // Gamma writing. - if (!render_target_cache_->gamma_render_target_as_srgb()) { + if (!(edram_rov_used || + render_target_cache_->gamma_render_target_as_unorm16())) { for (uint32_t i = 0; i < 4; ++i) { if (color_infos[i].color_format == xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA) { @@ -4600,9 +4599,7 @@ XE_NOINLINE void D3D12CommandProcessor::UpdateSystemConstantValues_Impl( } // Texture signedness / gamma. - bool gamma_render_target_as_srgb = - render_target_cache_->gamma_render_target_as_srgb(); - uint32_t textures_resolved = 0; + uint32_t textures_resolution_scaled = 0; uint32_t textures_remaining = used_texture_mask; uint32_t texture_index; while (xe::bit_scan_forward(textures_remaining, &texture_index)) { @@ -4621,15 +4618,14 @@ XE_NOINLINE void D3D12CommandProcessor::UpdateSystemConstantValues_Impl( texture_signs_uint = (texture_signs_uint & ~texture_signs_mask) | texture_signs_shifted; - // cache misses here, we're accessing the texture bindings out of order - textures_resolved |= - uint32_t(texture_cache_->IsActiveTextureResolved(texture_index)) + textures_resolution_scaled |= + uint32_t(texture_cache_->IsActiveTextureResolutionScaled(texture_index)) << texture_index; } - update_dirty_uint32_cmp(system_constants_.textures_resolved, - textures_resolved); - system_constants_.textures_resolved = textures_resolved; + update_dirty_uint32_cmp(system_constants_.textures_resolution_scaled, + textures_resolution_scaled); + system_constants_.textures_resolution_scaled = textures_resolution_scaled; // Log2 of sample count, for alpha to mask and with ROV, for EDRAM address // calculation with MSAA. diff --git a/src/xenia/gpu/d3d12/d3d12_render_target_cache.cc b/src/xenia/gpu/d3d12/d3d12_render_target_cache.cc index 728a3ea44..416c0817e 100644 --- a/src/xenia/gpu/d3d12/d3d12_render_target_cache.cc +++ b/src/xenia/gpu/d3d12/d3d12_render_target_cache.cc @@ -429,7 +429,7 @@ bool D3D12RenderTargetCache::Initialize() { if (path_ == Path::kHostRenderTargets) { // Host render targets. - gamma_render_target_as_srgb_ = cvars::gamma_render_target_as_srgb; + gamma_render_target_as_unorm16_ = cvars::gamma_render_target_as_unorm16; depth_float24_round_ = cvars::depth_float24_round; depth_float24_convert_in_pixel_shader_ = @@ -472,6 +472,17 @@ bool D3D12RenderTargetCache::Initialize() { break; } } + if (msaa_2x_supported_ && gamma_render_target_as_unorm16_) { + multisample_quality_levels.Format = DXGI_FORMAT_R16G16B16A16_UNORM; + multisample_quality_levels.NumQualityLevels = 0; + if (FAILED(device->CheckFeatureSupport( + D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS, + &multisample_quality_levels, + sizeof(multisample_quality_levels))) || + !multisample_quality_levels.NumQualityLevels) { + msaa_2x_supported_ = false; + } + } } else { msaa_2x_supported_ = false; } @@ -987,8 +998,8 @@ bool D3D12RenderTargetCache::Initialize() { } else if (path_ == Path::kPixelShaderInterlock) { // Pixel shader interlock (rasterizer-ordered view). - // Blending is done in linear space directly in shaders. - gamma_render_target_as_srgb_ = false; + // Piecewise linear gamma is 8-bit with programmable blending. + gamma_render_target_as_unorm16_ = false; // Always true float24 depth rounded to the nearest even. depth_float24_round_ = true; @@ -1762,12 +1773,10 @@ DXGI_FORMAT D3D12RenderTargetCache::GetColorResourceDXGIFormat( // compression. switch (format) { case xenos::ColorRenderTargetFormat::k_8_8_8_8: - case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA: - if (gamma_render_target_as_srgb_) { - // Can toggle between UNORM and UNORM_SRGB for the same data. - return DXGI_FORMAT_R8G8B8A8_TYPELESS; - } return DXGI_FORMAT_R8G8B8A8_UNORM; + case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA: + return gamma_render_target_as_unorm16_ ? DXGI_FORMAT_R16G16B16A16_UNORM + : DXGI_FORMAT_R8G8B8A8_UNORM; case xenos::ColorRenderTargetFormat::k_2_10_10_10: case xenos::ColorRenderTargetFormat::k_2_10_10_10_AS_10_10_10_10: return DXGI_FORMAT_R10G10B10A2_UNORM; @@ -1800,11 +1809,6 @@ DXGI_FORMAT D3D12RenderTargetCache::GetColorResourceDXGIFormat( DXGI_FORMAT D3D12RenderTargetCache::GetColorDrawDXGIFormat( xenos::ColorRenderTargetFormat format) const { switch (format) { - case xenos::ColorRenderTargetFormat::k_8_8_8_8: - return DXGI_FORMAT_R8G8B8A8_UNORM; - case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA: - return gamma_render_target_as_srgb_ ? DXGI_FORMAT_R8G8B8A8_UNORM_SRGB - : DXGI_FORMAT_R8G8B8A8_UNORM; case xenos::ColorRenderTargetFormat::k_16_16: return DXGI_FORMAT_R16G16_SNORM; case xenos::ColorRenderTargetFormat::k_16_16_16_16: @@ -1898,6 +1902,10 @@ DXGI_FORMAT D3D12RenderTargetCache::GetDepthSRVStencilDXGIFormat( } } +bool D3D12RenderTargetCache::IsGammaFormatHostStorageSeparate() const { + return gamma_render_target_as_unorm16_; +} + RenderTargetCache::RenderTarget* D3D12RenderTargetCache::CreateRenderTarget( RenderTargetKey key) { ID3D12Device* device = command_processor_.GetD3D12Provider().GetDevice(); @@ -1978,7 +1986,6 @@ RenderTargetCache::RenderTarget* D3D12RenderTargetCache::CreateRenderTarget( } D3D12_CPU_DESCRIPTOR_HANDLE descriptor_draw_handle = descriptor_draw.GetHandle(); - ui::d3d12::D3D12CpuDescriptorPool::Descriptor descriptor_draw_srgb; ui::d3d12::D3D12CpuDescriptorPool::Descriptor descriptor_load_separate; ui::d3d12::D3D12CpuDescriptorPool::Descriptor descriptor_srv_stencil; D3D12_SHADER_RESOURCE_VIEW_DESC srv_desc; @@ -2037,23 +2044,6 @@ RenderTargetCache::RenderTarget* D3D12RenderTargetCache::CreateRenderTarget( } device->CreateRenderTargetView(resource.Get(), &rtv_desc, descriptor_draw_handle); - // sRGB drawing RTV. - switch (key.GetColorFormat()) { - case xenos::ColorRenderTargetFormat::k_8_8_8_8: - case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA: - if (gamma_render_target_as_srgb_) { - descriptor_draw_srgb = descriptor_pool.AllocateDescriptor(); - if (!descriptor_draw_srgb.IsValid()) { - return nullptr; - } - rtv_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; - device->CreateRenderTargetView(resource.Get(), &rtv_desc, - descriptor_draw_srgb.GetHandle()); - } - break; - default: - break; - } // Ownership transfer RTV. DXGI_FORMAT load_format = GetColorOwnershipTransferDXGIFormat(key.GetColorFormat()); @@ -2074,9 +2064,8 @@ RenderTargetCache::RenderTarget* D3D12RenderTargetCache::CreateRenderTarget( return new D3D12RenderTarget( key, resource.Get(), std::move(descriptor_draw), - std::move(descriptor_draw_srgb), std::move(descriptor_load_separate), - std::move(descriptor_srv), std::move(descriptor_srv_stencil), - resource_state); + std::move(descriptor_load_separate), std::move(descriptor_srv), + std::move(descriptor_srv_stencil), resource_state); } bool D3D12RenderTargetCache::IsHostDepthEncodingDifferent( @@ -3417,15 +3406,25 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) { if (dest_is_64bpp) { // Handle construction of 64bpp color, either from two 32-bit samples in r0 - // and r1, or from one 64bpp sample in r1. Using r2.x as temporary when + // and r1, or from one 64bpp sample in r1. Using r2.xy as temporary when // needed. // If color_packed_in_r0x_and_r1x, use the generic path for combining two // 32-bit samples - as raw in r0.x and r1.x - into the destination. bool color_packed_in_r0x_and_r1x = false; if (source_is_color) { switch (source_color_format) { - case xenos::ColorRenderTargetFormat::k_8_8_8_8: case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA: { + // 8_8_8_8_GAMMA is represented by linear stored in + // R16G16B16A16_UNORM. + for (uint32_t i = 0; i < 2; ++i) { + for (uint32_t j = 0; j < 3; ++j) { + DxbcShaderTranslator::PreSaturatedLinearToPWLGamma(a, i, j, i, j, + 2, 0, 2, 1); + } + } + } + [[fallthrough]]; + case xenos::ColorRenderTargetFormat::k_8_8_8_8: { color_packed_in_r0x_and_r1x = true; for (uint32_t i = 0; i < 2; ++i) { a.OpMAd(dxbc::Dest::R(i), dxbc::Src::R(i), dxbc::Src::LF(255.0f), @@ -3576,7 +3575,14 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) { switch (source_color_format) { case xenos::ColorRenderTargetFormat::k_8_8_8_8: case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA: { + // 8_8_8_8_GAMMA is represented by linear stored in + // R16G16B16A16_UNORM. if (dest_is_stencil_bit) { + if (source_color_format == + xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA) { + DxbcShaderTranslator::PreSaturatedLinearToPWLGamma(a, 1, 0, 1, 0, + 2, 0, 2, 1); + } a.OpMAd(dxbc::Dest::R(1, 0b0001), dxbc::Src::R(1, dxbc::Src::kXXXX), dxbc::Src::LF(255.0f), dxbc::Src::LF(0.5f)); a.OpFToU(dxbc::Dest::R(1, 0b0001), @@ -3586,9 +3592,30 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) { xenos::ColorRenderTargetFormat::k_8_8_8_8 || dest_color_format == xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA)) { - // Same format - passthrough. + // Same format - only perform color space conversion. + if (dest_color_format != source_color_format) { + if (dest_color_format == + xenos::ColorRenderTargetFormat::k_8_8_8_8) { + for (uint32_t i = 0; i < 3; ++i) { + DxbcShaderTranslator::PreSaturatedLinearToPWLGamma( + a, 1, i, 1, i, 2, 0, 2, 1); + } + } else { + for (uint32_t i = 0; i < 3; ++i) { + DxbcShaderTranslator::PWLGammaToLinear(a, 1, i, 1, i, true, 2, + 0, 2, 1); + } + } + } a.OpMov(dxbc::Dest::O(0), dxbc::Src::R(1)); } else if (mode.output == TransferOutput::kDepth) { + if (source_color_format == + xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA) { + for (uint32_t i = 1; i < 3; ++i) { + DxbcShaderTranslator::PreSaturatedLinearToPWLGamma( + a, 1, i, 1, i, 2, 0, 2, 1); + } + } // When need only depth, not stencil, skip the red component. a.OpMAd(dxbc::Dest::R( 1, osgn_parameter_index_sv_stencil_ref != UINT32_MAX @@ -3613,6 +3640,13 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) { dxbc::Src::R(1, dxbc::Src::kYYYY)); } else { color_packed_in_r1x = true; + if (source_color_format == + xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA) { + for (uint32_t i = 0; i < 3; ++i) { + DxbcShaderTranslator::PreSaturatedLinearToPWLGamma( + a, 1, i, 1, i, 2, 0, 2, 1); + } + } a.OpMAd(dxbc::Dest::R(1), dxbc::Src::R(1), dxbc::Src::LF(255.0f), dxbc::Src::LF(0.5f)); a.OpFToU(dxbc::Dest::R(1), dxbc::Src::R(1)); @@ -3756,8 +3790,7 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) { // this is the end of the shader. if (color_packed_in_r1x) { switch (dest_color_format) { - case xenos::ColorRenderTargetFormat::k_8_8_8_8: - case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA: { + case xenos::ColorRenderTargetFormat::k_8_8_8_8: { a.OpUBFE(dxbc::Dest::R(1), dxbc::Src::LU(8), dxbc::Src::LU(0, 8, 16, 24), dxbc::Src::R(1, dxbc::Src::kXXXX)); @@ -3765,6 +3798,26 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) { a.OpMul(dxbc::Dest::O(0), dxbc::Src::R(1), dxbc::Src::LF(1.0f / 255.0f)); } break; + case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA: { + // 8_8_8_8_GAMMA is represented by linear stored in + // R16G16B16A16_UNORM. + a.OpUBFE(dxbc::Dest::R(1), dxbc::Src::LU(8), + dxbc::Src::LU(0, 8, 16, 24), + dxbc::Src::R(1, dxbc::Src::kXXXX)); + a.OpUToF(dxbc::Dest::R(1), dxbc::Src::R(1)); + a.OpMul(dxbc::Dest::R(1, 0b0111), dxbc::Src::R(1), + dxbc::Src::LF(1.0f / 255.0f)); + a.OpMul(dxbc::Dest::O(0, 0b1000), dxbc::Src::R(1), + dxbc::Src::LF(1.0f / 255.0f)); + for (uint32_t i = 0; i < 3; ++i) { + DxbcShaderTranslator::PWLGammaToLinear(a, 1, i, 1, i, true, 0, + 0, 0, 1); + } + // TODO(Triang3l): The `mov` can be eliminated by passing the + // destination to `PWLGammaToLinear` as `dxbc::Dest` rather than + // just the register index. + a.OpMov(dxbc::Dest::O(0, 0b0111), dxbc::Src::R(1)); + } break; case xenos::ColorRenderTargetFormat::k_2_10_10_10: case xenos::ColorRenderTargetFormat::k_2_10_10_10_AS_10_10_10_10: { a.OpUBFE(dxbc::Dest::R(1), dxbc::Src::LU(10, 10, 10, 2), @@ -5360,13 +5413,24 @@ void D3D12RenderTargetCache::PerformTransfersAndResolveClears( float color_clear_value[4] = {}; bool clear_via_drawing = false; switch (dest_rt_key.GetColorFormat()) { - case xenos::ColorRenderTargetFormat::k_8_8_8_8: - case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA: { + case xenos::ColorRenderTargetFormat::k_8_8_8_8: { for (uint32_t j = 0; j < 4; ++j) { color_clear_value[j] = ((clear_value >> (j * 8)) & 0xFF) * (1.0f / 0xFF); } } break; + case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA: { + // 8_8_8_8_GAMMA is represented by linear stored in + // R16G16B16A16_UNORM. + for (uint32_t j = 0; j < 4; ++j) { + color_clear_value[j] = + ((clear_value >> (j * 8)) & 0xFF) * (1.0f / 0xFF); + } + for (uint32_t j = 0; j < 3; ++j) { + color_clear_value[j] = + xenos::PWLGammaToLinear(color_clear_value[j]); + } + } break; case xenos::ColorRenderTargetFormat::k_2_10_10_10: case xenos::ColorRenderTargetFormat::k_2_10_10_10_AS_10_10_10_10: { for (uint32_t j = 0; j < 3; ++j) { @@ -5513,17 +5577,6 @@ void D3D12RenderTargetCache::SetCommandListRenderTargets( } } } - uint32_t render_targets_are_srgb; - if (gamma_render_target_as_srgb_) { - render_targets_are_srgb = last_update_accumulated_color_targets_are_gamma(); - if (are_current_command_list_render_targets_srgb_ != - render_targets_are_srgb) { - are_current_command_list_render_targets_srgb_ = render_targets_are_srgb; - are_current_command_list_render_targets_valid_ = false; - } - } else { - render_targets_are_srgb = 0; - } if (!are_current_command_list_render_targets_valid_) { std::memcpy(current_command_list_render_targets_, depth_and_color_render_targets, @@ -5550,10 +5603,7 @@ void D3D12RenderTargetCache::SetCommandListRenderTargets( : null_rtv_descriptor_ss_.GetHandle(); } auto& d3d12_rt = *static_cast(render_target); - rtv_handles[rtv_count++] = - (render_targets_are_srgb & (uint32_t(1) << i)) - ? d3d12_rt.descriptor_draw_srgb().GetHandle() - : d3d12_rt.descriptor_draw().GetHandle(); + rtv_handles[rtv_count++] = d3d12_rt.descriptor_draw().GetHandle(); } command_processor_.GetDeferredCommandList().D3DOMSetRenderTargets( rtv_count, rtv_handles, false, @@ -6240,7 +6290,6 @@ ID3D12PipelineState* D3D12RenderTargetCache::GetOrCreateDumpPipeline( } else { switch (key.GetColorFormat()) { case xenos::ColorRenderTargetFormat::k_8_8_8_8: - case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA: if (!source_is_uint) { a.OpMAd(dxbc::Dest::R(1), dxbc::Src::R(1), dxbc::Src::LF(255.0f), dxbc::Src::LF(0.5f)); @@ -6252,6 +6301,22 @@ ID3D12PipelineState* D3D12RenderTargetCache::GetOrCreateDumpPipeline( dxbc::Src::R(1, dxbc::Src::kXXXX)); } break; + case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA: + // 8_8_8_8_GAMMA is represented by linear stored in R16G16B16A16_UNORM. + assert_false(source_is_uint); + for (uint32_t i = 0; i < 3; ++i) { + DxbcShaderTranslator::PreSaturatedLinearToPWLGamma(a, 1, i, 1, i, 0, + 0, 0, 1); + } + a.OpMAd(dxbc::Dest::R(1), dxbc::Src::R(1), dxbc::Src::LF(255.0f), + dxbc::Src::LF(0.5f)); + a.OpFToU(dxbc::Dest::R(1), dxbc::Src::R(1)); + for (uint32_t i = 1; i < 4; ++i) { + a.OpBFI(dxbc::Dest::R(1, 0b0001), dxbc::Src::LU(8), + dxbc::Src::LU(i * 8), dxbc::Src::R(1).Select(i), + dxbc::Src::R(1, dxbc::Src::kXXXX)); + } + break; case xenos::ColorRenderTargetFormat::k_2_10_10_10: case xenos::ColorRenderTargetFormat::k_2_10_10_10_AS_10_10_10_10: if (!source_is_uint) { diff --git a/src/xenia/gpu/d3d12/d3d12_render_target_cache.h b/src/xenia/gpu/d3d12/d3d12_render_target_cache.h index d89e28e71..3a5dbaeb0 100644 --- a/src/xenia/gpu/d3d12/d3d12_render_target_cache.h +++ b/src/xenia/gpu/d3d12/d3d12_render_target_cache.h @@ -95,8 +95,8 @@ class D3D12RenderTargetCache final : public RenderTargetCache { // For host render targets. - bool gamma_render_target_as_srgb() const { - return gamma_render_target_as_srgb_; + bool gamma_render_target_as_unorm16() const { + return gamma_render_target_as_unorm16_; } // Using R16G16[B16A16]_SNORM, which are -1...1, not the needed -32...32. @@ -129,6 +129,8 @@ class D3D12RenderTargetCache final : public RenderTargetCache { xenos::DepthRenderTargetFormat format); protected: + bool IsGammaFormatHostStorageSeparate() const override; + uint32_t GetMaxRenderTargetWidth() const override { return D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION; } @@ -225,16 +227,13 @@ class D3D12RenderTargetCache final : public RenderTargetCache { class D3D12RenderTarget final : public RenderTarget { public: - // descriptor_draw_srgb is only used for k_8_8_8_8 render targets when host - // sRGB (gamma_render_target_as_srgb) is used. descriptor_load is present - // when the DXGI formats are different for drawing and bit-exact loading - // (for NaN pattern preservation across EDRAM tile ownership transfers in - // floating-point formats, and to distinguish between two -1 representations - // in snorm formats). + // descriptor_load is present when the DXGI formats are different for + // drawing and bit-exact loading (for NaN pattern preservation across EDRAM + // tile ownership transfers in floating-point formats, and to distinguish + // between two -1 representations in snorm formats). D3D12RenderTarget( RenderTargetKey key, ID3D12Resource* resource, ui::d3d12::D3D12CpuDescriptorPool::Descriptor&& descriptor_draw, - ui::d3d12::D3D12CpuDescriptorPool::Descriptor&& descriptor_draw_srgb, ui::d3d12::D3D12CpuDescriptorPool::Descriptor&& descriptor_load_separate, ui::d3d12::D3D12CpuDescriptorPool::Descriptor&& descriptor_srv, @@ -243,7 +242,6 @@ class D3D12RenderTargetCache final : public RenderTargetCache { : RenderTarget(key), resource_(resource), descriptor_draw_(std::move(descriptor_draw)), - descriptor_draw_srgb_(std::move(descriptor_draw_srgb)), descriptor_load_separate_(std::move(descriptor_load_separate)), descriptor_srv_(std::move(descriptor_srv)), descriptor_srv_stencil_(std::move(descriptor_srv_stencil)), @@ -254,10 +252,6 @@ class D3D12RenderTargetCache final : public RenderTargetCache { const { return descriptor_draw_; } - const ui::d3d12::D3D12CpuDescriptorPool::Descriptor& descriptor_draw_srgb() - const { - return descriptor_draw_srgb_; - } const ui::d3d12::D3D12CpuDescriptorPool::Descriptor& descriptor_srv() const { return descriptor_srv_; @@ -297,7 +291,6 @@ class D3D12RenderTargetCache final : public RenderTargetCache { private: Microsoft::WRL::ComPtr resource_; ui::d3d12::D3D12CpuDescriptorPool::Descriptor descriptor_draw_; - ui::d3d12::D3D12CpuDescriptorPool::Descriptor descriptor_draw_srgb_; ui::d3d12::D3D12CpuDescriptorPool::Descriptor descriptor_load_separate_; // Texture SRV non-shader-visible descriptors, to prepare shader-visible // descriptors faster, by copying rather than by creating every time. @@ -718,7 +711,7 @@ class D3D12RenderTargetCache final : public RenderTargetCache { bool use_stencil_reference_output_ = false; - bool gamma_render_target_as_srgb_ = false; + bool gamma_render_target_as_unorm16_ = false; bool depth_float24_round_ = false; bool depth_float24_convert_in_pixel_shader_ = false; @@ -753,7 +746,6 @@ class D3D12RenderTargetCache final : public RenderTargetCache { const RenderTarget* const* current_command_list_render_targets_[1 + xenos::kMaxColorRenderTargets]; - uint32_t are_current_command_list_render_targets_srgb_ = 0; bool are_current_command_list_render_targets_valid_ = false; // Temporary storage for descriptors used in PerformTransfersAndResolveClears diff --git a/src/xenia/gpu/d3d12/d3d12_shared_memory.cc b/src/xenia/gpu/d3d12/d3d12_shared_memory.cc index 3a8a07d8b..43af29826 100644 --- a/src/xenia/gpu/d3d12/d3d12_shared_memory.cc +++ b/src/xenia/gpu/d3d12/d3d12_shared_memory.cc @@ -461,7 +461,7 @@ bool D3D12SharedMemory::UploadRanges( return false; } MakeRangeValid(upload_range_start << page_size_log2(), - uint32_t(upload_buffer_size), false, false); + uint32_t(upload_buffer_size), false); if (upload_buffer_size < (1ULL << 32) && upload_buffer_size > 8192) { memory::vastcpy( @@ -469,9 +469,8 @@ bool D3D12SharedMemory::UploadRanges( memory().TranslatePhysical(upload_range_start << page_size_log2()), static_cast(upload_buffer_size)); swcache::WriteFence(); - } else { - memcpy( + std::memcpy( upload_buffer_mapping, memory().TranslatePhysical(upload_range_start << page_size_log2()), upload_buffer_size); diff --git a/src/xenia/gpu/d3d12/pipeline_cache.cc b/src/xenia/gpu/d3d12/pipeline_cache.cc index 2f95233a9..4924a5d09 100644 --- a/src/xenia/gpu/d3d12/pipeline_cache.cc +++ b/src/xenia/gpu/d3d12/pipeline_cache.cc @@ -96,7 +96,8 @@ PipelineCache::PipelineCache(D3D12CommandProcessor& command_processor, shader_translator_ = std::make_unique( provider.GetAdapterVendorID(), bindless_resources_used_, edram_rov_used, - render_target_cache_.gamma_render_target_as_srgb(), + !(edram_rov_used || + render_target_cache_.gamma_render_target_as_unorm16()), render_target_cache_.msaa_2x_supported(), render_target_cache_.draw_resolution_scale_x(), render_target_cache_.draw_resolution_scale_y(), @@ -1100,7 +1101,8 @@ void PipelineCache::TranslateShadersForStorage( StringBuffer ucode_disasm_buffer; DxbcShaderTranslator translator( provider.GetAdapterVendorID(), bindless_resources_used_, edram_rov_used, - render_target_cache_.gamma_render_target_as_srgb(), + !(edram_rov_used || + render_target_cache_.gamma_render_target_as_unorm16()), render_target_cache_.msaa_2x_supported(), render_target_cache_.draw_resolution_scale_x(), render_target_cache_.draw_resolution_scale_y(), @@ -3219,7 +3221,8 @@ void PipelineCache::CreationThread(size_t thread_index) { StringBuffer ucode_disasm_buffer; DxbcShaderTranslator translator( provider.GetAdapterVendorID(), bindless_resources_used_, edram_rov_used, - render_target_cache_.gamma_render_target_as_srgb(), + !(edram_rov_used || + render_target_cache_.gamma_render_target_as_unorm16()), render_target_cache_.msaa_2x_supported(), render_target_cache_.draw_resolution_scale_x(), render_target_cache_.draw_resolution_scale_y(), diff --git a/src/xenia/gpu/draw_util.cc b/src/xenia/gpu/draw_util.cc index 6d5b86df2..54bf697b8 100644 --- a/src/xenia/gpu/draw_util.cc +++ b/src/xenia/gpu/draw_util.cc @@ -1135,12 +1135,12 @@ bool GetResolveInfo(const RegisterFile& regs, const Memory& memory, auto rb_copy_dest_pitch = regs.Get(); const uint32_t copy_dest_pitch_aligned = xe::align(rb_copy_dest_pitch.copy_dest_pitch, - texture_address::kStoragePitchHeightAlignmentElements); + texture_address::kStoragePitchHeightAlignmentBlocks); info_out.copy_dest_coordinate_info.pitch_aligned_div_32 = copy_dest_pitch_aligned >> 5; const uint32_t copy_dest_height_aligned = xe::align(rb_copy_dest_pitch.copy_dest_height, - texture_address::kStoragePitchHeightAlignmentElements); + texture_address::kStoragePitchHeightAlignmentBlocks); info_out.copy_dest_coordinate_info.height_aligned_div_32 = copy_dest_height_aligned >> 5; const FormatInfo& dest_format_info = *FormatInfo::Get(dest_format); diff --git a/src/xenia/gpu/dxbc_shader_translator.cc b/src/xenia/gpu/dxbc_shader_translator.cc index 864b826f9..d41cfca77 100644 --- a/src/xenia/gpu/dxbc_shader_translator.cc +++ b/src/xenia/gpu/dxbc_shader_translator.cc @@ -65,7 +65,7 @@ using namespace ucode; DxbcShaderTranslator::DxbcShaderTranslator( ui::GraphicsProvider::GpuVendorID vendor_id, bool bindless_resources_used, - bool edram_rov_used, bool gamma_render_target_as_srgb, + bool edram_rov_used, bool gamma_render_target_as_unorm8, bool msaa_2x_supported, uint32_t draw_resolution_scale_x, uint32_t draw_resolution_scale_y, bool force_emit_source_map) : a_(shader_code_, statistics_), @@ -73,7 +73,7 @@ DxbcShaderTranslator::DxbcShaderTranslator( vendor_id_(vendor_id), bindless_resources_used_(bindless_resources_used), edram_rov_used_(edram_rov_used), - gamma_render_target_as_srgb_(gamma_render_target_as_srgb), + gamma_render_target_as_unorm8_(gamma_render_target_as_unorm8), msaa_2x_supported_(msaa_2x_supported), draw_resolution_scale_x_(draw_resolution_scale_x), draw_resolution_scale_y_(draw_resolution_scale_y), @@ -222,9 +222,10 @@ void DxbcShaderTranslator::PopSystemTemp(uint32_t count) { } void DxbcShaderTranslator::PWLGammaToLinear( - uint32_t target_temp, uint32_t target_temp_component, uint32_t source_temp, - uint32_t source_temp_component, bool source_pre_saturated, uint32_t temp1, - uint32_t temp1_component, uint32_t temp2, uint32_t temp2_component) { + dxbc::Assembler& a, uint32_t target_temp, uint32_t target_temp_component, + uint32_t source_temp, uint32_t source_temp_component, + bool source_pre_saturated, uint32_t temp1, uint32_t temp1_component, + uint32_t temp2, uint32_t temp2_component) { // The source is needed only once to begin building the result, so it can be // the same as the destination. assert_true(temp1 != target_temp || temp1_component != target_temp_component); @@ -245,25 +246,25 @@ void DxbcShaderTranslator::PWLGammaToLinear( // Using `source >= threshold` comparisons because the input might have not // been saturated yet, and thus it may be NaN - since it will be saturated to // 0 later, the 0...64/255 case should be selected for it. - a_.OpGE(temp2_dest, source_src, dxbc::Src::LF(96.0f / 255.0f)); - a_.OpIf(true, temp2_src); + a.OpGE(temp2_dest, source_src, dxbc::Src::LF(96.0f / 255.0f)); + a.OpIf(true, temp2_src); // [96/255 ... 1 - a_.OpGE(temp2_dest, source_src, dxbc::Src::LF(192.0f / 255.0f)); - a_.OpMovC(temp1_dest, temp2_src, dxbc::Src::LF(8.0f / 1024.0f), - dxbc::Src::LF(4.0f / 1024.0f)); - a_.OpMovC(temp2_dest, temp2_src, dxbc::Src::LF(-1024.0f), - dxbc::Src::LF(-256.0f)); - a_.OpElse(); + a.OpGE(temp2_dest, source_src, dxbc::Src::LF(192.0f / 255.0f)); + a.OpMovC(temp1_dest, temp2_src, dxbc::Src::LF(8.0f / 1024.0f), + dxbc::Src::LF(4.0f / 1024.0f)); + a.OpMovC(temp2_dest, temp2_src, dxbc::Src::LF(-1024.0f), + dxbc::Src::LF(-256.0f)); + a.OpElse(); // 0 ... 96/255) - a_.OpGE(temp2_dest, source_src, dxbc::Src::LF(64.0f / 255.0f)); - a_.OpMovC(temp1_dest, temp2_src, dxbc::Src::LF(2.0f / 1024.0f), - dxbc::Src::LF(1.0f / 1024.0f)); - a_.OpMovC(temp2_dest, temp2_src, dxbc::Src::LF(-64.0f), dxbc::Src::LF(0.0f)); - a_.OpEndIf(); + a.OpGE(temp2_dest, source_src, dxbc::Src::LF(64.0f / 255.0f)); + a.OpMovC(temp1_dest, temp2_src, dxbc::Src::LF(2.0f / 1024.0f), + dxbc::Src::LF(1.0f / 1024.0f)); + a.OpMovC(temp2_dest, temp2_src, dxbc::Src::LF(-64.0f), dxbc::Src::LF(0.0f)); + a.OpEndIf(); if (!source_pre_saturated) { // Saturate the input, and flush NaN to 0. - a_.OpMov(target_dest, source_src, true); + a.OpMov(target_dest, source_src, true); } // linear = gamma * (255 * 1024) * scale + offset // As both 1024 and the scale are powers of 2, and 1024 * scale is not smaller @@ -272,22 +273,22 @@ void DxbcShaderTranslator::PWLGammaToLinear( // gamma * (255 * 1024 * scale) - or the option chosen here, as long as // 1024 is applied before the scale since the scale is < 1 (specifically at // least 1/1024), and it may make very small values denormal. - a_.OpMul(target_dest, source_pre_saturated ? source_src : target_src, - dxbc::Src::LF(255.0f * 1024.0f)); - a_.OpMAd(target_dest, target_src, temp1_src, temp2_src); + a.OpMul(target_dest, source_pre_saturated ? source_src : target_src, + dxbc::Src::LF(255.0f * 1024.0f)); + a.OpMAd(target_dest, target_src, temp1_src, temp2_src); // linear += trunc(linear * scale) - a_.OpMul(temp1_dest, target_src, temp1_src); - a_.OpRoundZ(temp1_dest, temp1_src); - a_.OpAdd(target_dest, target_src, temp1_src); + a.OpMul(temp1_dest, target_src, temp1_src); + a.OpRoundZ(temp1_dest, temp1_src); + a.OpAdd(target_dest, target_src, temp1_src); // linear *= 1/1023 - a_.OpMul(target_dest, target_src, dxbc::Src::LF(1.0f / 1023.0f)); + a.OpMul(target_dest, target_src, dxbc::Src::LF(1.0f / 1023.0f)); } void DxbcShaderTranslator::PreSaturatedLinearToPWLGamma( - uint32_t target_temp, uint32_t target_temp_component, uint32_t source_temp, - uint32_t source_temp_component, uint32_t temp_or_target, - uint32_t temp_or_target_component, uint32_t temp_non_target, - uint32_t temp_non_target_component) { + dxbc::Assembler& a, uint32_t target_temp, uint32_t target_temp_component, + uint32_t source_temp, uint32_t source_temp_component, + uint32_t temp_or_target, uint32_t temp_or_target_component, + uint32_t temp_non_target, uint32_t temp_non_target_component) { // The source may be the same as the target, but in this case it can't also be // used as a temporary variable. assert_true(target_temp != source_temp || @@ -317,28 +318,28 @@ void DxbcShaderTranslator::PreSaturatedLinearToPWLGamma( // Get the scale (into temp_or_target) and the offset (into temp_non_target) // for the piece. - a_.OpGE(temp_non_target_dest, source_src, dxbc::Src::LF(128.0f / 1023.0f)); - a_.OpIf(true, temp_non_target_src); + a.OpGE(temp_non_target_dest, source_src, dxbc::Src::LF(128.0f / 1023.0f)); + a.OpIf(true, temp_non_target_src); // [128/1023 ... 1 - a_.OpGE(temp_non_target_dest, source_src, dxbc::Src::LF(512.0f / 1023.0f)); - a_.OpMovC(temp_or_target_dest, temp_non_target_src, - dxbc::Src::LF(1023.0f / 8.0f), dxbc::Src::LF(1023.0f / 4.0f)); - a_.OpMovC(temp_non_target_dest, temp_non_target_src, - dxbc::Src::LF(128.0f / 255.0f), dxbc::Src::LF(64.0f / 255.0f)); - a_.OpElse(); + a.OpGE(temp_non_target_dest, source_src, dxbc::Src::LF(512.0f / 1023.0f)); + a.OpMovC(temp_or_target_dest, temp_non_target_src, + dxbc::Src::LF(1023.0f / 8.0f), dxbc::Src::LF(1023.0f / 4.0f)); + a.OpMovC(temp_non_target_dest, temp_non_target_src, + dxbc::Src::LF(128.0f / 255.0f), dxbc::Src::LF(64.0f / 255.0f)); + a.OpElse(); // 0 ... 128/1023) - a_.OpGE(temp_non_target_dest, source_src, dxbc::Src::LF(64.0f / 1023.0f)); - a_.OpMovC(temp_or_target_dest, temp_non_target_src, - dxbc::Src::LF(1023.0f / 2.0f), dxbc::Src::LF(1023.0f)); - a_.OpMovC(temp_non_target_dest, temp_non_target_src, - dxbc::Src::LF(32.0f / 255.0f), dxbc::Src::LF(0.0f)); - a_.OpEndIf(); + a.OpGE(temp_non_target_dest, source_src, dxbc::Src::LF(64.0f / 1023.0f)); + a.OpMovC(temp_or_target_dest, temp_non_target_src, + dxbc::Src::LF(1023.0f / 2.0f), dxbc::Src::LF(1023.0f)); + a.OpMovC(temp_non_target_dest, temp_non_target_src, + dxbc::Src::LF(32.0f / 255.0f), dxbc::Src::LF(0.0f)); + a.OpEndIf(); // gamma = trunc(linear * scale) * (1.0 / 255.0) + offset - a_.OpMul(target_dest, source_src, temp_or_target_src); - a_.OpRoundZ(target_dest, target_src); - a_.OpMAd(target_dest, target_src, dxbc::Src::LF(1.0f / 255.0f), - temp_non_target_src); + a.OpMul(target_dest, source_src, temp_or_target_src); + a.OpRoundZ(target_dest, target_src); + a.OpMAd(target_dest, target_src, dxbc::Src::LF(1.0f / 255.0f), + temp_non_target_src); } void DxbcShaderTranslator::RemapAndConvertVertexIndices( @@ -2135,7 +2136,8 @@ constexpr DxbcShaderTranslator::SystemConstantRdef {"xe_texture_swizzled_signs", ShaderRdefTypeIndex::kUint4Array2, sizeof(uint32_t) * 4 * 2}, - {"xe_textures_resolved", ShaderRdefTypeIndex::kUint, sizeof(uint32_t)}, + {"xe_textures_resolution_scaled", ShaderRdefTypeIndex::kUint, + sizeof(uint32_t)}, {"xe_sample_count_log2", ShaderRdefTypeIndex::kUint2, sizeof(uint32_t) * 2}, {"xe_alpha_test_reference", ShaderRdefTypeIndex::kFloat, sizeof(float)}, diff --git a/src/xenia/gpu/dxbc_shader_translator.h b/src/xenia/gpu/dxbc_shader_translator.h index 6af1ce0b0..2b5e8599c 100644 --- a/src/xenia/gpu/dxbc_shader_translator.h +++ b/src/xenia/gpu/dxbc_shader_translator.h @@ -49,7 +49,7 @@ class DxbcShaderTranslator : public ShaderTranslator { public: DxbcShaderTranslator(ui::GraphicsProvider::GpuVendorID vendor_id, bool bindless_resources_used, bool edram_rov_used, - bool gamma_render_target_as_srgb = false, + bool gamma_render_target_as_unorm8 = false, bool msaa_2x_supported = true, uint32_t draw_resolution_scale_x = 1, uint32_t draw_resolution_scale_y = 1, @@ -316,9 +316,8 @@ class DxbcShaderTranslator : public ShaderTranslator { // components of each of the 32 used texture fetch constants. uint32_t texture_swizzled_signs[8]; - // Whether the contents of each texture in fetch constants comes from a - // resolve operation. - uint32_t textures_resolved; + // Whether each texture in fetch constants contains resolution-scaled data. + uint32_t textures_resolution_scaled; // Log2 of X and Y sample size. Used for alpha to mask, and for MSAA with // ROV, this is used for EDRAM address calculation. uint32_t sample_count_log2[2]; @@ -425,7 +424,7 @@ class DxbcShaderTranslator : public ShaderTranslator { kTextureSwizzledSigns, - kTexturesResolved, + kTexturesResolutionScaled, kSampleCountLog2, kAlphaTestReference, @@ -578,6 +577,26 @@ class DxbcShaderTranslator : public ShaderTranslator { uint32_t temp2_temp_component, bool remap_to_0_to_0_5); + // Converts one scalar from piecewise linear gamma to linear. The target may + // be the same as the source, the temporary variables must be different. If + // the source is not pre-saturated, saturation will be done internally. + static void PWLGammaToLinear(dxbc::Assembler& a, uint32_t target_temp, + uint32_t target_temp_component, + uint32_t source_temp, + uint32_t source_temp_component, + bool source_pre_saturated, uint32_t temp1, + uint32_t temp1_component, uint32_t temp2, + uint32_t temp2_component); + // Converts one scalar, which must be saturated before calling this function, + // from linear to piecewise linear gamma. The target may be the same as either + // the source or as temp_or_target, but not as both (and temp_or_target may + // not be the same as the source). temp_non_target must be different. + static void PreSaturatedLinearToPWLGamma( + dxbc::Assembler& a, uint32_t target_temp, uint32_t target_temp_component, + uint32_t source_temp, uint32_t source_temp_component, + uint32_t temp_or_target, uint32_t temp_or_target_component, + uint32_t temp_non_target, uint32_t temp_non_target_component); + protected: void Reset() override; @@ -689,24 +708,6 @@ class DxbcShaderTranslator : public ShaderTranslator { // inactive. void ExportToMemory(uint8_t export_eM); - // Converts one scalar from piecewise linear gamma to linear. The target may - // be the same as the source, the temporary variables must be different. If - // the source is not pre-saturated, saturation will be done internally. - void PWLGammaToLinear(uint32_t target_temp, uint32_t target_temp_component, - uint32_t source_temp, uint32_t source_temp_component, - bool source_pre_saturated, uint32_t temp1, - uint32_t temp1_component, uint32_t temp2, - uint32_t temp2_component); - // Converts one scalar, which must be saturated before calling this function, - // from linear to piecewise linear gamma. The target may be the same as either - // the source or as temp_or_target, but not as both (and temp_or_target may - // not be the same as the source). temp_non_target must be different. - void PreSaturatedLinearToPWLGamma( - uint32_t target_temp, uint32_t target_temp_component, - uint32_t source_temp, uint32_t source_temp_component, - uint32_t temp_or_target, uint32_t temp_or_target_component, - uint32_t temp_non_target, uint32_t temp_non_target_component); - bool IsSampleRate() const { assert_true(is_pixel_shader()); return DSV_IsWritingFloat24Depth() && !current_shader().writes_depth(); @@ -977,8 +978,9 @@ class DxbcShaderTranslator : public ShaderTranslator { bool edram_rov_used_; // Whether with RTV-based output-merger, k_8_8_8_8_GAMMA render targets are - // represented as host sRGB. - bool gamma_render_target_as_srgb_; + // represented as host 8-bit unsigned normalized, and require conversion in + // translated shaders. + bool gamma_render_target_as_unorm8_; // Whether 2x MSAA is emulated using real 2x MSAA rather than two samples of // 4x MSAA. diff --git a/src/xenia/gpu/dxbc_shader_translator_fetch.cc b/src/xenia/gpu/dxbc_shader_translator_fetch.cc index 21c8c2eb3..562a14fd6 100644 --- a/src/xenia/gpu/dxbc_shader_translator_fetch.cc +++ b/src/xenia/gpu/dxbc_shader_translator_fetch.cc @@ -1076,12 +1076,12 @@ void DxbcShaderTranslator::ProcessTextureFetchInstruction( if (normalized_components_with_scaled_offsets) { // Using coord_and_sampler_temp.w as a temporary for the needed // resolution scale inverse - sampler not loaded yet. - a_.OpAnd( - dxbc::Dest::R(coord_and_sampler_temp, 0b1000), - LoadSystemConstant(SystemConstants::Index::kTexturesResolved, - offsetof(SystemConstants, textures_resolved), - dxbc::Src::kXXXX), - dxbc::Src::LU(uint32_t(1) << tfetch_index)); + a_.OpAnd(dxbc::Dest::R(coord_and_sampler_temp, 0b1000), + LoadSystemConstant( + SystemConstants::Index::kTexturesResolutionScaled, + offsetof(SystemConstants, textures_resolution_scaled), + dxbc::Src::kXXXX), + dxbc::Src::LU(uint32_t(1) << tfetch_index)); a_.OpIf(true, dxbc::Src::R(coord_and_sampler_temp, dxbc::Src::kWWWW)); a_.OpAdd( dxbc::Dest::R(coord_and_sampler_temp, @@ -1145,12 +1145,12 @@ void DxbcShaderTranslator::ProcessTextureFetchInstruction( if (normalized_components_with_scaled_offsets) { // Using coord_and_sampler_temp.w as a temporary for the needed // resolution scale inverse - sampler not loaded yet. - a_.OpAnd( - dxbc::Dest::R(coord_and_sampler_temp, 0b1000), - LoadSystemConstant(SystemConstants::Index::kTexturesResolved, - offsetof(SystemConstants, textures_resolved), - dxbc::Src::kXXXX), - dxbc::Src::LU(uint32_t(1) << tfetch_index)); + a_.OpAnd(dxbc::Dest::R(coord_and_sampler_temp, 0b1000), + LoadSystemConstant( + SystemConstants::Index::kTexturesResolutionScaled, + offsetof(SystemConstants, textures_resolution_scaled), + dxbc::Src::kXXXX), + dxbc::Src::LU(uint32_t(1) << tfetch_index)); a_.OpIf(true, dxbc::Src::R(coord_and_sampler_temp, dxbc::Src::kWWWW)); a_.OpMAd(dxbc::Dest::R(coord_and_sampler_temp, normalized_components_with_scaled_offsets), @@ -2121,56 +2121,9 @@ void DxbcShaderTranslator::ProcessTextureFetchInstruction( a_.OpBreak(); a_.OpCase(dxbc::Src::LU(uint32_t(xenos::TextureSign::kGamma))); uint32_t gamma_temp = PushSystemTemp(); - if (gamma_render_target_as_srgb_) { - // Check if the texture has sRGB rather that piecewise linear gamma. - // More likely that it's just a texture with PWL, put this case in the - // `if`, with `else` for sRGB resolved render targets. - a_.OpAnd( - dxbc::Dest::R(gamma_temp, 0b0001), - LoadSystemConstant(SystemConstants::Index::kTexturesResolved, - offsetof(SystemConstants, textures_resolved), - dxbc::Src::kXXXX), - dxbc::Src::LU(uint32_t(1) << tfetch_index)); - a_.OpIf(false, dxbc::Src::R(gamma_temp, dxbc::Src::kXXXX)); - } // Convert from piecewise linear. - PWLGammaToLinear(system_temp_result_, i, system_temp_result_, i, false, - gamma_temp, 0, gamma_temp, 1); - if (gamma_render_target_as_srgb_) { - a_.OpElse(); - // Convert from sRGB. - a_.OpMov(component_dest, component_src, true); - a_.OpGE(dxbc::Dest::R(gamma_temp, 0b0001), - dxbc::Src::LF(RenderTargetCache::kSrgbToLinearThreshold), - component_src); - a_.OpIf(true, dxbc::Src::R(gamma_temp, dxbc::Src::kXXXX)); - // sRGB <= kSrgbToLinearThreshold case - linear scale. - a_.OpMul(component_dest, component_src, - dxbc::Src::LF(1.0f / - RenderTargetCache::kSrgbToLinearDenominator1)); - a_.OpElse(); - // sRGB > kSrgbToLinearThreshold case. - // 0 and 1 must be exactly achievable - only convert when the - // saturated value is < 1. - a_.OpLT(dxbc::Dest::R(gamma_temp, 0b0001), component_src, - dxbc::Src::LF(1.0f)); - a_.OpIf(true, dxbc::Src::R(gamma_temp, dxbc::Src::kXXXX)); - a_.OpMAd(component_dest, component_src, - dxbc::Src::LF(1.0f / - RenderTargetCache::kSrgbToLinearDenominator2), - dxbc::Src::LF(RenderTargetCache::kSrgbToLinearOffset / - RenderTargetCache::kSrgbToLinearDenominator2)); - a_.OpLog(component_dest, component_src); - a_.OpMul(component_dest, component_src, - dxbc::Src::LF(RenderTargetCache::kSrgbToLinearExponent)); - a_.OpExp(component_dest, component_src); - // Close the < 1 check. - a_.OpEndIf(); - // Close the sRGB <= kSrgbToLinearThreshold check. - a_.OpEndIf(); - // Close the PWL or sRGB check. - a_.OpEndIf(); - } + PWLGammaToLinear(a_, system_temp_result_, i, system_temp_result_, i, + false, gamma_temp, 0, gamma_temp, 1); // Release gamma_temp. PopSystemTemp(); a_.OpBreak(); diff --git a/src/xenia/gpu/dxbc_shader_translator_om.cc b/src/xenia/gpu/dxbc_shader_translator_om.cc index 001be04b4..a181008ed 100644 --- a/src/xenia/gpu/dxbc_shader_translator_om.cc +++ b/src/xenia/gpu/dxbc_shader_translator_om.cc @@ -1197,7 +1197,7 @@ void DxbcShaderTranslator::ROV_UnpackColor( dxbc::Src::LF(1.0f / 255.0f)); if (i) { for (uint32_t j = 0; j < 3; ++j) { - PWLGammaToLinear(color_temp, j, color_temp, j, true, temp1, + PWLGammaToLinear(a_, color_temp, j, color_temp, j, true, temp1, temp1_component, temp2, temp2_component); } } @@ -1350,7 +1350,7 @@ void DxbcShaderTranslator::ROV_PackPreClampedColor( : xenos::ColorRenderTargetFormat::k_8_8_8_8))); for (uint32_t j = 0; j < 4; ++j) { if (i && j < 3) { - PreSaturatedLinearToPWLGamma(temp1, temp1_component, color_temp, j, + PreSaturatedLinearToPWLGamma(a_, temp1, temp1_component, color_temp, j, temp1, temp1_component, temp2, temp2_component); // Denormalize and add 0.5 for rounding. @@ -1685,7 +1685,7 @@ void DxbcShaderTranslator::CompletePixelShader_WriteToRTVs() { SystemConstants::Index::kColorExpBias, offsetof(SystemConstants, color_exp_bias) + sizeof(float) * i, dxbc::Src::kXXXX)); - if (!gamma_render_target_as_srgb_) { + if (gamma_render_target_as_unorm8_) { // Convert to gamma space - this is incorrect, since it must be done after // blending on the Xbox 360, but this is just one of many blending issues // in the RTV path. @@ -1696,8 +1696,9 @@ void DxbcShaderTranslator::CompletePixelShader_WriteToRTVs() { a_.OpMov(dxbc::Dest::R(system_temp_color, 0b0111), dxbc::Src::R(system_temp_color), true); for (uint32_t j = 0; j < 3; ++j) { - PreSaturatedLinearToPWLGamma(system_temp_color, j, system_temp_color, j, - gamma_temp, 0, gamma_temp, 1); + PreSaturatedLinearToPWLGamma(a_, system_temp_color, j, + system_temp_color, j, gamma_temp, 0, + gamma_temp, 1); } a_.OpEndIf(); } diff --git a/src/xenia/gpu/render_target_cache.cc b/src/xenia/gpu/render_target_cache.cc index e8887535e..b8574954e 100644 --- a/src/xenia/gpu/render_target_cache.cc +++ b/src/xenia/gpu/render_target_cache.cc @@ -144,16 +144,15 @@ DEFINE_bool( "into account for render-to-texture, for more correct shadow filtering, " "bloom, etc., in some cases.", "GPU"); -// Disabled by default because of full-screen effects that occur when game -// shaders assume piecewise linear (4541080F), much more severe than -// blending-related issues. DEFINE_bool( - gamma_render_target_as_srgb, false, - "When the host can't write piecewise linear gamma directly with correct " - "blending, use sRGB output on the host for conceptually correct blending " - "in linear color space while having slightly different precision " - "distribution in the render target and severely incorrect values if the " - "game accesses the resulting colors directly as raw data.", + gamma_render_target_as_unorm16, true, + "When the host can't write 8 bits per component pixels with piecewise " + "linear gamma encoding directly with correct blending, use the 16-bit " + "unsigned normalized format, if supported, for conceptually correct " + "8_8_8_8_GAMMA render target format blending in linear color space. " + "Greatly increases accuracy for this format, but may result in render " + "target copying costs if the game switches between 8_8_8_8_GAMMA and " + "8_8_8_8 views for the same EDRAM render target.", "GPU"); DEFINE_bool( mrt_edram_used_range_clamp_to_min, true, @@ -630,7 +629,6 @@ bool RenderTargetCache::Update(bool is_rasterization_done, uint32_t edram_bases[1 + xenos::kMaxColorRenderTargets]; uint32_t resource_formats[1 + xenos::kMaxColorRenderTargets]; uint32_t rts_are_64bpp = 0; - uint32_t color_rts_are_gamma = 0; if (is_rasterization_done) { if (normalized_depth_control.z_enable || normalized_depth_control.stencil_enable) { @@ -659,9 +657,6 @@ bool RenderTargetCache::Update(bool is_rasterization_done, if (is_64bpp) { rts_are_64bpp |= uint32_t(1) << rt_bit_index; } - if (color_format == xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA) { - color_rts_are_gamma |= uint32_t(1) << i; - } xenos::ColorRenderTargetFormat color_resource_format; if (interlock_barrier_only) { // Only changes in mapping between coordinates and addresses are @@ -747,7 +742,6 @@ bool RenderTargetCache::Update(bool is_rasterization_done, if (!are_accumulated_render_targets_valid_) { std::memset(last_update_accumulated_render_targets_, 0, sizeof(last_update_accumulated_render_targets_)); - last_update_accumulated_color_targets_are_gamma_ = 0; } return true; } @@ -958,26 +952,13 @@ bool RenderTargetCache::Update(bool is_rasterization_done, std::memcpy(last_update_accumulated_render_targets_, last_update_used_render_targets_, sizeof(last_update_accumulated_render_targets_)); - last_update_accumulated_color_targets_are_gamma_ = 0; are_accumulated_render_targets_valid_ = true; } - // Only update color space of render targets that actually matter here, don't - // disable gamma emulation (which may require ending the render pass) on the - // host, for example, if making a depth-only draw between color draws with a - // gamma target. - uint32_t color_rts_used_bits = depth_and_color_rts_used_bits >> 1; - // Ignore any render targets dropped before in this function for any reason. - color_rts_are_gamma &= color_rts_used_bits; - last_update_accumulated_color_targets_are_gamma_ = - (last_update_accumulated_color_targets_are_gamma_ & - ~color_rts_used_bits) | - color_rts_are_gamma; return true; } uint32_t RenderTargetCache::GetLastUpdateBoundRenderTargets( - bool distinguish_gamma_formats, uint32_t* depth_and_color_formats_out) const { if (GetPath() != Path::kHostRenderTargets) { if (depth_and_color_formats_out) { @@ -998,12 +979,7 @@ uint32_t RenderTargetCache::GetLastUpdateBoundRenderTargets( } rts_used |= uint32_t(1) << i; if (depth_and_color_formats_out) { - depth_and_color_formats_out[i] = - (distinguish_gamma_formats && i && - (last_update_accumulated_color_targets_are_gamma_ & - (uint32_t(1) << (i - 1)))) - ? uint32_t(xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA) - : render_target->key().resource_format; + depth_and_color_formats_out[i] = render_target->key().resource_format; } } return rts_used; diff --git a/src/xenia/gpu/render_target_cache.h b/src/xenia/gpu/render_target_cache.h index dc075e707..29fbc4373 100644 --- a/src/xenia/gpu/render_target_cache.h +++ b/src/xenia/gpu/render_target_cache.h @@ -32,7 +32,7 @@ DECLARE_bool(depth_transfer_not_equal_test); DECLARE_bool(depth_float24_round); DECLARE_bool(depth_float24_convert_in_pixel_shader); DECLARE_bool(draw_resolution_scaled_texture_offsets); -DECLARE_bool(gamma_render_target_as_srgb); +DECLARE_bool(gamma_render_target_as_unorm16); DECLARE_bool(native_2x_msaa); DECLARE_bool(native_stencil_value_output); DECLARE_bool(snorm16_render_target_full_range); @@ -72,12 +72,16 @@ class RenderTargetCache { // - 16_16_FLOAT, k_16_16_16_16_FLOAT - the Xenos float16 doesn't have // special values. // Significant differences: - // - 8_8_8_8_GAMMA - the piecewise linear gamma curve is very different than - // sRGB, one possible path is conversion in shaders (resulting in - // incorrect blending, especially visible on decals in 4D5307E6), another - // is using sRGB render targets and either conversion on resolve or - // reading the resolved data as a true sRGB texture (incorrect when the - // game accesses the data directly, like 4541080F). + // - 8_8_8_8_GAMMA - the piecewise linear gamma precision distribution + // encoding is very different from sRGB. Linear space blending can be + // obtained by promoting to R16G16B16A16_UNORM, but for compact storage, + // conversion in pixel shader output may be done, though it results in + // incorrect blending, especially visible on decals in 4D5307E6. Emulating + // by replacing the encoding with sRGB for render target writes and + // resolved texture reads could work for some games, but certain games, + // such as 4541080F, perform piecewise gamma encoding calculations in + // their code, and that produces noticeably incorrect results if the + // encoding is changed in guest texture memory. // - 2_10_10_10_FLOAT - ranges significantly different than in float16, much // smaller RGB range, and alpha is fixed-point and has only 2 bits. // - 16_16, 16_16_16_16 - has -32 to 32 range, not -1 to 1 - need either to @@ -91,28 +95,6 @@ class RenderTargetCache { kPixelShaderInterlock, }; - // Useful host-specific values. - // sRGB conversion from the Direct3D 11.3 functional specification. - static constexpr float kSrgbToLinearDenominator1 = 12.92f; - static constexpr float kSrgbToLinearDenominator2 = 1.055f; - static constexpr float kSrgbToLinearExponent = 2.4f; - static constexpr float kSrgbToLinearOffset = 0.055f; - static constexpr float kSrgbToLinearThreshold = 0.04045f; - static constexpr float SrgbToLinear(float srgb) { - // 0 and 1 must be exactly achievable, also convert NaN to 0. - if (!(srgb > 0.0f)) { - return 0.0f; - } - if (!(srgb < 1.0f)) { - return 1.0f; - } - if (srgb <= kSrgbToLinearThreshold) { - return srgb / kSrgbToLinearDenominator1; - } - return std::pow((srgb + kSrgbToLinearOffset) / kSrgbToLinearDenominator2, - kSrgbToLinearExponent); - } - // Pixel shader interlock implementation helpers. // Appended to the format in the format constant via bitwise OR. @@ -221,7 +203,6 @@ class RenderTargetCache { // formats (resource formats, but if needed, with gamma taken into account) of // each. uint32_t GetLastUpdateBoundRenderTargets( - bool distinguish_gamma_formats, uint32_t* depth_and_color_formats_out = nullptr) const; protected: @@ -238,6 +219,8 @@ class RenderTargetCache { const RegisterFile& register_file() const { return register_file_; } + virtual bool IsGammaFormatHostStorageSeparate() const = 0; + // Call last in implementation-specific initialization (when things like path // are initialized by the implementation). void InitializeCommon(); @@ -274,7 +257,7 @@ class RenderTargetCache { uint32_t pitch_tiles_at_32bpp : 8; // 19 xenos::MsaaSamples msaa_samples : xenos::kMsaaSamplesBits; // 21 uint32_t is_depth : 1; // 22 - // Ignoring the blending precision and sRGB. + // Ignoring the blending precision. uint32_t resource_format : xenos::kRenderTargetFormatBits; // 26 }; @@ -548,10 +531,6 @@ class RenderTargetCache { assert_true(GetPath() == Path::kHostRenderTargets); return last_update_accumulated_render_targets_; } - uint32_t last_update_accumulated_color_targets_are_gamma() const { - assert_true(GetPath() == Path::kHostRenderTargets); - return last_update_accumulated_color_targets_are_gamma_; - } const std::vector* last_update_transfers() const { assert_true(GetPath() == Path::kHostRenderTargets); @@ -697,11 +676,10 @@ class RenderTargetCache { } }; - static constexpr xenos::ColorRenderTargetFormat GetColorResourceFormat( - xenos::ColorRenderTargetFormat format) { - // sRGB, if used on the host, is a view property or global state - linear - // and sRGB host render targets can share data directly without transfers. - if (format == xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA) { + xenos::ColorRenderTargetFormat GetColorResourceFormat( + xenos::ColorRenderTargetFormat format) const { + if (format == xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA && + !IsGammaFormatHostStorageSeparate()) { return xenos::ColorRenderTargetFormat::k_8_8_8_8; } return xenos::GetStorageColorFormat(format); @@ -755,10 +733,6 @@ class RenderTargetCache { RenderTarget* last_update_accumulated_render_targets_[1 + xenos::kMaxColorRenderTargets]; - // Whether the color render targets (in bits 0...3) from the last successful - // update have k_8_8_8_8_GAMMA format, for sRGB emulation on the host if - // needed. - uint32_t last_update_accumulated_color_targets_are_gamma_; // If false, the next update must copy last_update_used_render_targets_ to // last_update_accumulated_render_targets_ - it's not beneficial or even // incorrect to keep the previously bound render targets. diff --git a/src/xenia/gpu/shaders/resolve.xesli b/src/xenia/gpu/shaders/resolve.xesli index 85ed9cf10..b7ec4f034 100644 --- a/src/xenia/gpu/shaders/resolve.xesli +++ b/src/xenia/gpu/shaders/resolve.xesli @@ -161,7 +161,7 @@ uint XeResolveEdramPixelStrideInts(XeResolveInfo resolve_info) { #ifndef XE_RESOLVE_CLEAR uint XeResolveDestPixelAddress(const XeResolveInfo resolve_info, uint2_xe host_position, - const uint bytes_per_element_log2) { + const uint bytes_per_block_log2) { host_position += resolve_info.dest_xy_offset_scaled; uint address; uint2_xe guest_position = host_position; @@ -170,19 +170,18 @@ uint XeResolveEdramPixelStrideInts(XeResolveInfo resolve_info) { resolution_scaled_addressing = XeniaTextureGetResolutionScaledAddressing( host_position.xy, resolve_info.resolution_scale, - bytes_per_element_log2); + bytes_per_block_log2); guest_position = resolution_scaled_addressing.guest_group_origin; #endif dont_flatten_xe if (resolve_info.dest_is_array) { address = uint(XenosTextureTiledAddress3D( int3_xe(uint3_xe(guest_position, resolve_info.dest_slice)), resolve_info.dest_row_pitch_macro_tiles, - resolve_info.dest_slice_pitch_3d_macro_tiles, - bytes_per_element_log2)); + resolve_info.dest_slice_pitch_3d_macro_tiles, bytes_per_block_log2)); } else { address = uint(XenosTextureTiledAddress2D( int2_xe(guest_position), resolve_info.dest_row_pitch_macro_tiles, - bytes_per_element_log2)); + bytes_per_block_log2)); } #ifdef XE_RESOLVE_RESOLUTION_SCALED address = address * (resolve_info.resolution_scale.x * @@ -195,18 +194,18 @@ uint XeResolveEdramPixelStrideInts(XeResolveInfo resolve_info) { // XOR to apply to the byte address to flip the bits corresponding to the // given X coordinate bits within the macro tile width, or with resolution - // scaling, within XeniaTextureResolutionScaledGroupElements.x. + // scaling, within XeniaTextureResolutionScaledGroupBlocks.x. // Addition is recommended instead of XOR if the bits are known to be 0 in the // original address, so the host GPU driver can optimize it into a constant // store offset if available in the host hardware shader instruction set // architecture. uint XeResolveLocalXAddressXor(const uint x, - const uint bytes_per_element_log2) { + const uint bytes_per_block_log2) { #ifdef XE_RESOLVE_RESOLUTION_SCALED - return x << bytes_per_element_log2; + return x << bytes_per_block_log2; #else return uint(XenosTextureTiledAddressXInMacroXor(int(x), - bytes_per_element_log2)); + bytes_per_block_log2)); #endif } diff --git a/src/xenia/gpu/shaders/texture_address.xesli b/src/xenia/gpu/shaders/texture_address.xesli index a555c4642..4b76fb478 100644 --- a/src/xenia/gpu/shaders/texture_address.xesli +++ b/src/xenia/gpu/shaders/texture_address.xesli @@ -26,15 +26,15 @@ int XenosTextureTiledAddressCombine(const int outer_inner_bytes, const int bank, } int XenosTextureTiledAddress2D(const int2_xe p, const uint pitch_macro_tiles, - const uint bytes_per_element_log2) { - const int outer_elements = + const uint bytes_per_block_log2) { + const int outer_blocks = ((p.y >> XENOS_TEXTURE_MACRO_TILE_HEIGHT_2D_LOG2) * int(pitch_macro_tiles) + (p.x >> XENOS_TEXTURE_MACRO_TILE_WIDTH_LOG2)) << 6; - const int inner_elements = (((p.y >> 1) & 0x7) << 3) | (p.x & 0x7); - const int outer_inner_bytes = - (outer_elements | inner_elements) << bytes_per_element_log2; + const int inner_blocks = (((p.y >> 1) & 0x7) << 3) | (p.x & 0x7); + const int outer_inner_bytes = (outer_blocks | inner_blocks) + << bytes_per_block_log2; const int bank = (p.y >> 4) & 0x1; const int pipe = ((p.x >> 3) & 0x3) ^ (((p.y >> 3) & 0x1) << 1); return XenosTextureTiledAddressCombine(outer_inner_bytes, bank, pipe, @@ -43,18 +43,18 @@ int XenosTextureTiledAddress2D(const int2_xe p, const uint pitch_macro_tiles, int XenosTextureTiledAddress3D(const int3_xe p, const uint pitch_macro_tiles, const uint height_macro_tiles, - const uint bytes_per_element_log2) { - const int outer_elements = + const uint bytes_per_block_log2) { + const int outer_blocks = ((((p.z >> XENOS_TEXTURE_MACRO_TILE_DEPTH_LOG2) * int(height_macro_tiles) + (p.y >> XENOS_TEXTURE_MACRO_TILE_HEIGHT_3D_LOG2)) * int(pitch_macro_tiles)) + (p.x >> XENOS_TEXTURE_MACRO_TILE_WIDTH_LOG2)) << 7; - const int inner_elements = + const int inner_blocks = ((p.z & 0x3) << 5) | (((p.y >> 1) & 0x3) << 3) | (p.x & 0x7); - const int outer_inner_bytes = - (outer_elements | inner_elements) << bytes_per_element_log2; + const int outer_inner_bytes = (outer_blocks | inner_blocks) + << bytes_per_block_log2; const int bank = ((p.y >> 3) ^ (p.z >> 2)) & 0x1; const int pipe = ((p.x >> 3) & 0x3) ^ (bank << 1); return XenosTextureTiledAddressCombine(outer_inner_bytes, bank, pipe, @@ -65,49 +65,49 @@ int XenosTextureTiledAddress3D(const int3_xe p, const uint pitch_macro_tiles, // X coordinate bits within the width of a macro tile. // Note that in a tiled address, bit 7 is X[4] ^ Y[3] ^ Z[2], not X[4] alone. int XenosTextureTiledAddressXInMacroXor(const int x, - const uint bytes_per_element_log2) { - return XenosTextureTiledAddressCombine((x & 0x7) << bytes_per_element_log2, + const uint bytes_per_block_log2) { + return XenosTextureTiledAddressCombine((x & 0x7) << bytes_per_block_log2, 0, (x >> 3) & 0x3, 0); } -// The lowest bits of an element index within a micro tile are X[2:0]. +// The lowest bits of an block index within a micro tile are X[2:0]. // In a tiled address, the bit 4 is always Y[0]. -// However, the bits [3:0] are the lower bits of the micro tile element index -// times the number of bytes per element. -// Because of this, a number of elements, that depends on the count of bytes per -// element, along the X axis (aligned to this amount) is stored consecutively in +// However, the bits [3:0] are the lower bits of the micro tile block index +// times the number of bytes per block. +// Because of this, a number of blocks, that depends on the count of bytes per +// block, along the X axis (aligned to this amount) is stored consecutively in // guest memory: -// - 1bpe: 8 elements (8 bytes - limited by address bit 3 being Y[1] for 1bpe). -// - 2bpe: 8 elements (16 bytes - limited by address bit 4 always being Y[0]). -// - 4bpe: 4 elements. -// - 8bpe: 2 elements. -// - 16bpe: 1 element. -// This makes it possible to access multiple elements in a single row using -// 8-byte or (for >= 2bpe) 16-byte loads and stores, and that's particularly -// useful when transferring texture data between tiled and linear storage. +// - 1bpb: 8 blocks (8 bytes - limited by address bit 3 being Y[1] for 1bpb). +// - 2bpb: 8 blocks (16 bytes - limited by address bit 4 always being Y[0]). +// - 4bpb: 4 blocks. +// - 8bpb: 2 blocks. +// - 16bpb: 1 block. +// This makes it possible to access multiple blocks in a single row using 8-byte +// or (for >= 2bpb) 16-byte loads and stores, and that's particularly useful +// when transferring texture data between tiled and linear storage. // With resolution scaling, one scaled group of bytes in guest addresses // corresponds to `scale.x * scale.y` groups of the same size on the host. // -// A single group contains a full rectangular region of elements. This means -// that, for instance, if the Y[1] tiled address bit is within the group size, -// Y[0] must be within it too, so division is enough to go from host to guest +// A single group contains a full rectangular region of blocks. This means that, +// for instance, if the Y[1] tiled address bit is within the group size, Y[0] +// must be within it too, so division is enough to go from host to guest // coordinates for the origin of the group. // // The address of the guest group on the host is the guest tiled address of its // origin in guest coordinates multiplied by `scale.x * scale.y`. // -// Within a guest group, the addressing of elements is controlled by the host. -// Specifically, host groups are arranged in a guest group as block-linear -// column-major (for storage locality along both axes), and elements in a host +// Within a guest group, the addressing of blocks is controlled by the host. +// Specifically, host groups are arranged in a guest group as group-linear +// column-major (for storage locality along both axes), and blocks in a host // group are laid out as linear row-major (guest tiling therefore is applied // only to whole guest groups, not within them, for simplicity). // // Addressing with resolution scaling is not intended to allow for -// reinterpretation of resolution-scaled data between different numbers of bits -// per element. Rather, it's designed for simple and efficient access on the -// host, primarily when copying between tiled and linear storage, and to reduce -// the differences in shader logic between unscaled and scaled data. +// reinterpretation of resolution-scaled data between different numbers of bytes +// per block. Rather, it's designed for simple and efficient access on the host, +// primarily when copying between tiled and linear storage, and to reduce the +// differences in shader logic between unscaled and scaled data. // // However, the groups are still small enough to preserve most of the tiling // properties on a macro level, most importantly the possibility to resolve @@ -120,33 +120,33 @@ int XenosTextureTiledAddressXInMacroXor(const int x, // resolution-scaled data in a memory range to be done with the number of bytes // per pixel being the only needed metadata. // -// A common pattern in Xenia is copying multiple 8-byte or (for >= 2bpe) 16-byte -// sequences of consecutive elements along the X axis in a single shader +// A common pattern in Xenia is copying multiple 8-byte or (for >= 2bpb) 16-byte +// sequences of consecutive blocks along the X axis in a single shader // invocation, by computing the tiled address once and merely flipping X bits in // it. // // With the resolution scaling group size being no larger than 2^7 bytes, it may -// contain guest X bits [3:0] for <= 4bpe, [1:0] for 8bpe, and [0] for 16bpe -// (note that though X[3] always goes to address[6], for 8bpe, X[2] is -// address[8], so a group can't be wider than 4 elements, and similarly for X[1] -// for 16bpe). +// contain guest X bits [3:0] for <= 4bpb, [1:0] for 8bpb, and [0] for 16bpb +// (note that though X[3] always goes to address[6], for 8bpb, X[2] is +// address[8], so a group can't be wider than 4 blocks, and similarly for X[1] +// for 16bpb). // // Given these requirements, the group sizes are chosen as follows: -// - 1bpe - lower 7 bits of an unscaled address are X0, X1, X2, Y1, Y0, Y2, X3: -// - Group width: 2^4 elements (maximum within 7 bits), or 2^4 bytes. -// - Group height: 2^3 elements (Y[2:0] between X[3:0]). -// - 2bpe - lower 7 bits of an unscaled address are 0, X0, X1, X2, Y0, Y1, X3: -// - Group width: 2^4 elements (maximum within 7 bits), or 2^5 bytes. -// - Group height: 2^2 elements (Y[1:0] between X[3:0]). -// - 4bpe - lower 7 bits of an unscaled address are 0, 0, X0, X1, Y0, X2, X3: -// - Group width: 2^4 elements (maximum within 7 bits), or 2^6 bytes. -// - Group height: 2^1 elements (Y[0] between X[3:0]). -// - 8bpe - lower 7 bits of an unscaled address are 0, 0, 0, X0, Y0, X1, X3: -// - Group width: 2^2 elements (X[2] is beyond 7 bits), or 2^5 bytes. -// - Group height: 2^1 elements (Y[0] between X[1:0]). -// - 16bpe - lower 7 bits of an unscaled address are 0, 0, 0, 0, Y0, X0, X3: -// - Group width: 2^1 elements (X[2:1] is beyond 7 bits), or 2^5 bytes. -// - Group height: 2^1 elements (Y[0] below X[0]). +// - 1bpb - lower 7 bits of an unscaled address are X0, X1, X2, Y1, Y0, Y2, X3: +// - Group width: 2^4 blocks (maximum within 7 bits), or 2^4 bytes. +// - Group height: 2^3 blocks (Y[2:0] between X[3:0]). +// - 2bpb - lower 7 bits of an unscaled address are 0, X0, X1, X2, Y0, Y1, X3: +// - Group width: 2^4 blocks (maximum within 7 bits), or 2^5 bytes. +// - Group height: 2^2 blocks (Y[1:0] between X[3:0]). +// - 4bpb - lower 7 bits of an unscaled address are 0, 0, X0, X1, Y0, X2, X3: +// - Group width: 2^4 blocks (maximum within 7 bits), or 2^6 bytes. +// - Group height: 2^1 blocks (Y[0] between X[3:0]). +// - 8bpb - lower 7 bits of an unscaled address are 0, 0, 0, X0, Y0, X1, X3: +// - Group width: 2^2 blocks (X[2] is beyond 7 bits), or 2^5 bytes. +// - Group height: 2^1 blocks (Y[0] between X[1:0]). +// - 16bpb - lower 7 bits of an unscaled address are 0, 0, 0, 0, Y0, X0, X3: +// - Group width: 2^1 blocks (X[2:1] is beyond 7 bits), or 2^5 bytes. +// - Group height: 2^1 blocks (Y[0] below X[0]). // // 2^6 bytes copied per invocation is likely to be optimal, as that consumes 16 // 32-bit VGPRs, out of a total of 24 (1024 / 40 rounded down to 4) available @@ -154,19 +154,17 @@ int XenosTextureTiledAddressXInMacroXor(const int x, // (although the occupancy of copy shaders is likely to be limited by memory // accesses instead anyway). // -// Note that with the given group sizes, as well as with elements in a host -// group stored as row-major, for 1bpe, 16x1 host elements are stored -// consecutively with resolution scaling (even though in guest tiling, only 8x1 -// elements are), so they can be accessed via one 16-byte operation rather than -// two 8-byte ones. +// Note that with the given group sizes, as well as with blocks in a host group +// stored as row-major, for 1bpb, 16x1 host blocks are stored consecutively with +// resolution scaling (even though in guest tiling, only 8x1 blocks are), so +// they can be accessed via one 16-byte operation rather than two 8-byte ones. // Expected to be called for a compile-time constant. -uint2_xe XeniaTextureResolutionScaledGroupElementsLog2( - const uint bytes_per_element_log2) { +uint2_xe XeniaTextureResolutionScaledGroupBlocksLog2( + const uint bytes_per_block_log2) { // Based on the tiled address properties, see the comment above for details. - return uint2_xe( - bytes_per_element_log2 >= 3u ? 5u - bytes_per_element_log2 : 4u, - 3u - min(bytes_per_element_log2, 2u)); + return uint2_xe(bytes_per_block_log2 >= 3u ? 5u - bytes_per_block_log2 : 4u, + 3u - min(bytes_per_block_log2, 2u)); } struct XeniaTextureResolutionScaledAddressing { @@ -177,20 +175,20 @@ struct XeniaTextureResolutionScaledAddressing { XeniaTextureResolutionScaledAddressing XeniaTextureGetResolutionScaledAddressing(const uint2_xe position, const uint2_xe resolution_scale, - const uint bytes_per_element_log2) { + const uint bytes_per_block_log2) { XeniaTextureResolutionScaledAddressing addressing; - const uint2_xe group_elements_log2 = - XeniaTextureResolutionScaledGroupElementsLog2(bytes_per_element_log2); + const uint2_xe group_blocks_log2 = + XeniaTextureResolutionScaledGroupBlocksLog2(bytes_per_block_log2); - const uint2_xe host_group_id_in_texture = position >> group_elements_log2; + const uint2_xe host_group_id_in_texture = position >> group_blocks_log2; const uint2_xe guest_group_id_in_texture = host_group_id_in_texture / resolution_scale; const uint2_xe host_group_id_in_guest_group = host_group_id_in_texture - resolution_scale * guest_group_id_in_texture; addressing.guest_group_origin = - guest_group_id_in_texture << group_elements_log2; + guest_group_id_in_texture << group_blocks_log2; // Host groups are stored as column-major in a guest group, but this can be // changed freely. @@ -198,18 +196,18 @@ XeniaTextureGetResolutionScaledAddressing(const uint2_xe position, host_group_id_in_guest_group.x * resolution_scale.y + host_group_id_in_guest_group.y; // Shifts are expanded rather than chained because the number of bytes per - // element, and thus also the group size, are expected to be compile-time + // block, and thus also the group size, are expected to be compile-time // constants, so this is expected to be combined using GPU bitfield insert // instructions. const uint group_width_bytes_log2 = - group_elements_log2.x + bytes_per_element_log2; + group_blocks_log2.x + bytes_per_block_log2; const uint2_xe position_in_host_group = - position & ((uint_x2_xe(1u) << group_elements_log2) - 1u); + position & ((uint_x2_xe(1u) << group_blocks_log2) - 1u); addressing.host_byte_offset_in_guest_group = (host_group_index_in_guest_group << - (group_width_bytes_log2 + group_elements_log2.y)) | + (group_width_bytes_log2 + group_blocks_log2.y)) | (position_in_host_group.y << group_width_bytes_log2) | - (position_in_host_group.x << bytes_per_element_log2); + (position_in_host_group.x << bytes_per_block_log2); return addressing; } diff --git a/src/xenia/gpu/shaders/texture_load.xesli b/src/xenia/gpu/shaders/texture_load.xesli index f2a28cb59..cc1b3664e 100644 --- a/src/xenia/gpu/shaders/texture_load.xesli +++ b/src/xenia/gpu/shaders/texture_load.xesli @@ -98,14 +98,14 @@ XeTextureLoadInfo XeTextureLoadGetInfo(param_push_consts_xe) { uint XeTextureLoadSourceAddress(const XeTextureLoadInfo load_info, const uint3_xe host_position, - const uint bytes_per_element_log2) { + const uint bytes_per_block_log2) { uint address; uint3_xe guest_position = host_position; #ifdef XE_TEXTURE_LOAD_RESOLUTION_SCALED const XeniaTextureResolutionScaledAddressing resolution_scaled_addressing = XeniaTextureGetResolutionScaledAddressing(host_position.xy, load_info.resolution_scale, - bytes_per_element_log2); + bytes_per_block_log2); guest_position.xy = resolution_scaled_addressing.guest_group_origin; #else dont_flatten_xe if (!load_info.is_tiled) { @@ -114,7 +114,7 @@ uint XeTextureLoadSourceAddress(const XeTextureLoadInfo load_info, (guest_position.y + load_info.guest_z_stride_block_rows_aligned * guest_position.z)) << - bytes_per_element_log2; + bytes_per_block_log2; } else #endif { @@ -124,12 +124,12 @@ uint XeTextureLoadSourceAddress(const XeTextureLoadInfo load_info, load_info.guest_pitch_aligned >> XENOS_TEXTURE_MACRO_TILE_WIDTH_LOG2, load_info.guest_z_stride_block_rows_aligned >> XENOS_TEXTURE_MACRO_TILE_HEIGHT_3D_LOG2, - bytes_per_element_log2)); + bytes_per_block_log2)); } else { address = uint(XenosTextureTiledAddress2D( int2_xe(guest_position.xy), load_info.guest_pitch_aligned >> XENOS_TEXTURE_MACRO_TILE_WIDTH_LOG2, - bytes_per_element_log2)); + bytes_per_block_log2)); } } #ifdef XE_TEXTURE_LOAD_RESOLUTION_SCALED @@ -143,24 +143,24 @@ uint XeTextureLoadSourceAddress(const XeTextureLoadInfo load_info, // XOR to apply to the byte address to flip the bits corresponding to the given // X coordinate bits within: -// - Resolution-scaled tiled: XeniaTextureResolutionScaledGroupElements.x; +// - Resolution-scaled tiled: XeniaTextureResolutionScaledGroupBlocks.x; // - Unscaled tiled: macro tile width; // - Linear: 256 bytes. // Addition is recommended instead of XOR if the bits are known to be 0 in the // original address, so the host GPU driver can optimize it into a constant load // offset if available in the host hardware shader instruction set architecture. uint XeTextureLoadLocalXAddressXor(const uint x, - const uint bytes_per_element_log2, + const uint bytes_per_block_log2, const bool is_tiled) { uint x_address_xor; #ifndef XE_TEXTURE_LOAD_RESOLUTION_SCALED dont_flatten_xe if (is_tiled) { x_address_xor = uint( - XenosTextureTiledAddressXInMacroXor(int(x), bytes_per_element_log2)); + XenosTextureTiledAddressXInMacroXor(int(x), bytes_per_block_log2)); } else #endif { - x_address_xor = x << bytes_per_element_log2; + x_address_xor = x << bytes_per_block_log2; } return x_address_xor; } diff --git a/src/xenia/gpu/shaders/xenos_draw.hlsli b/src/xenia/gpu/shaders/xenos_draw.hlsli index 840662e27..a866ad460 100644 --- a/src/xenia/gpu/shaders/xenos_draw.hlsli +++ b/src/xenia/gpu/shaders/xenos_draw.hlsli @@ -23,7 +23,7 @@ cbuffer xe_system_cbuffer : register(b0) { uint4 xe_texture_swizzled_signs[2]; - uint xe_textures_resolved; + uint xe_textures_resolution_scaled; uint2 xe_sample_count_log2; float xe_alpha_test_reference; diff --git a/src/xenia/gpu/shared_memory.cc b/src/xenia/gpu/shared_memory.cc index a6f2847c2..94fc5fe78 100644 --- a/src/xenia/gpu/shared_memory.cc +++ b/src/xenia/gpu/shared_memory.cc @@ -28,12 +28,12 @@ bool SharedMemory::InitializeCommon() { ((kBufferSize >> page_size_log2_) + 63) / 64; num_system_page_flags_ = static_cast(num_system_page_flags_entries); - // Allocate double-buffered valid flags (2x) plus gpu_resolved and gpu_written - // (1x each) = 4 total arrays. With 2048 entries per array and 8 bytes per - // entry, that's 65536 bytes total (adds 16KB for double buffering). + // Allocate double-buffered valid flags (2x) plus gpu_written (1x) = 3 total + // arrays. With 2048 entries per array and 8 bytes per entry, that's 49152 + // bytes total. uint64_t* system_page_flags_base = (uint64_t*)memory::AllocFixed( - nullptr, num_system_page_flags_ * 4 * sizeof(uint64_t), + nullptr, num_system_page_flags_ * 3 * sizeof(uint64_t), memory::AllocationType::kReserveCommit, memory::PageAccess::kReadWrite); if (!system_page_flags_base) { @@ -44,16 +44,12 @@ bool SharedMemory::InitializeCommon() { // Set up double buffer for valid flags valid_buffer_a_ = system_page_flags_base; valid_buffer_b_ = system_page_flags_base + num_system_page_flags_; - system_page_flags_valid_and_gpu_resolved_ = - system_page_flags_base + (num_system_page_flags_ * 2); system_page_flags_valid_and_gpu_written_ = - system_page_flags_base + (num_system_page_flags_ * 3); + system_page_flags_base + (num_system_page_flags_ * 2); - // Initialize both valid buffers to zero + // Initialize all buffers to zero memset(valid_buffer_a_, 0, 8 * num_system_page_flags_entries); memset(valid_buffer_b_, 0, 8 * num_system_page_flags_entries); - memset(system_page_flags_valid_and_gpu_resolved_, 0, - 8 * num_system_page_flags_entries); memset(system_page_flags_valid_and_gpu_written_, 0, 8 * num_system_page_flags_entries); @@ -125,7 +121,6 @@ void SharedMemory::ShutdownCommon() { staging_valid_flags_.store(nullptr, std::memory_order_relaxed); } - system_page_flags_valid_and_gpu_resolved_ = nullptr; system_page_flags_valid_and_gpu_written_ = nullptr; num_system_page_flags_ = 0; } @@ -141,8 +136,6 @@ void SharedMemory::InvalidateAllPages() { std::memset(staging, 0, num_system_page_flags_ * sizeof(uint64_t)); std::memset(system_page_flags_valid_and_gpu_written_, 0, num_system_page_flags_ * sizeof(uint64_t)); - std::memset(system_page_flags_valid_and_gpu_resolved_, 0, - num_system_page_flags_ * sizeof(uint64_t)); // Mark all blocks as dirty and set dirty flag dirty_blocks_.store(0xFFFFFFFF, std::memory_order_relaxed); @@ -370,8 +363,7 @@ void SharedMemory::FireWatches(uint32_t page_first, uint32_t page_last, } } -void SharedMemory::RangeWrittenByGpu(uint32_t start, uint32_t length, - bool is_resolve) { +void SharedMemory::RangeWrittenByGpu(uint32_t start, uint32_t length) { if (length == 0 || start >= kBufferSize) { return; } @@ -386,7 +378,7 @@ void SharedMemory::RangeWrittenByGpu(uint32_t start, uint32_t length, // Mark the range as valid (so pages are not reuploaded until modified by the // CPU) and watch it so the CPU can reuse it and this will be caught. - MakeRangeValid(start, length, true, is_resolve); + MakeRangeValid(start, length, true); } bool SharedMemory::AllocateSparseHostGpuMemoryRange( @@ -398,9 +390,7 @@ bool SharedMemory::AllocateSparseHostGpuMemoryRange( } void SharedMemory::MakeRangeValid(uint32_t start, uint32_t length, - bool written_by_gpu, - bool written_by_gpu_resolve) { - assert_false(written_by_gpu_resolve && !written_by_gpu); + bool written_by_gpu) { if (length == 0 || start >= kBufferSize) { return; } @@ -438,11 +428,6 @@ void SharedMemory::MakeRangeValid(uint32_t start, uint32_t length, } else { system_page_flags_valid_and_gpu_written_[i] &= ~valid_bits; } - if (written_by_gpu_resolve) { - system_page_flags_valid_and_gpu_resolved_[i] |= valid_bits; - } else { - system_page_flags_valid_and_gpu_resolved_[i] &= ~valid_bits; - } } } @@ -476,14 +461,11 @@ void SharedMemory::UnlinkWatchRange(WatchRange* range) { range->next_free = watch_range_first_free_; watch_range_first_free_ = range; } + // todo: optimize, an enormous amount of cpu time (1.34%) is spent here. -bool SharedMemory::RequestRange(uint32_t start, uint32_t length, - bool* any_data_resolved_out) { +bool SharedMemory::RequestRange(uint32_t start, uint32_t length) { if (!length) { // Some texture or buffer is empty, for example - safe to draw in this case. - if (any_data_resolved_out) { - *any_data_resolved_out = false; - } return true; } if (start > kBufferSize || (kBufferSize - start) < length) { @@ -526,9 +508,6 @@ bool SharedMemory::RequestRange(uint32_t start, uint32_t length, } if (all_valid) { // All pages already valid, nothing to upload - if (any_data_resolved_out) { - *any_data_resolved_out = false; - } return true; } } @@ -538,23 +517,18 @@ bool SharedMemory::RequestRange(uint32_t start, uint32_t length, std::pair* uploads = reinterpret_cast*>(upload_ranges_.data()); - bool any_data_resolved = false; // swcache::PrefetchL1(&system_page_flags_[block_first]); uint32_t range_start = UINT32_MAX; { auto global_lock = global_critical_region_.Acquire(); TryFindUploadRange(block_first, block_last, page_first, page_last, - any_data_resolved, range_start, current_upload_range, - uploads); + range_start, current_upload_range, uploads); } if (range_start != UINT32_MAX) { uploads[current_upload_range++] = (std::make_pair(range_start, page_last + 1 - range_start)); } - if (any_data_resolved_out) { - *any_data_resolved_out = any_data_resolved; - } if (!current_upload_range) { return true; } @@ -575,7 +549,6 @@ void SharedMemory::TryFindUploadRange(const uint32_t& block_first, const uint32_t& block_last, const uint32_t& page_first, const uint32_t& page_last, - bool& any_data_resolved, uint32_t& range_start, unsigned int& current_upload_range, std::pair* uploads) { @@ -584,29 +557,14 @@ void SharedMemory::TryFindUploadRange(const uint32_t& block_first, uint64_t* valid_flags = active_valid_flags_.load(std::memory_order_relaxed); for (uint32_t i = block_first; i <= block_last; ++i) { - // const SystemPageFlagsBlock& block = system_page_flags_[i]; uint64_t block_valid = valid_flags[i]; - uint64_t block_resolved = 0; - - if (any_data_resolved) { - block_resolved = 0; - } else { - block_resolved = system_page_flags_valid_and_gpu_resolved_[i]; - } if (i == block_first) { uint64_t block_before = mod_shift_left(uint64_t(1), page_first) - 1; block_valid |= block_before; - block_resolved &= ~block_before; } if (i == block_last && (page_last & 63) != 63) { uint64_t block_inside = mod_shift_left(uint64_t(1), page_last + 1) - 1; block_valid |= ~block_inside; - block_resolved &= block_inside; - } - // Consider pages in the block outside the requested range valid. - if (!block_resolved) { - } else { - any_data_resolved = true; } TryGetNextUploadRange(range_start, block_valid, i, current_upload_range, uploads); @@ -728,7 +686,6 @@ std::pair SharedMemory::MemoryInvalidationCallback( } uint64_t* valid_flags = active_valid_flags_.load(std::memory_order_relaxed); valid_flags[i] &= ~invalidate_bits; - system_page_flags_valid_and_gpu_resolved_[i] &= ~invalidate_bits; system_page_flags_valid_and_gpu_written_[i] &= ~invalidate_bits; // Track which 64-entry blocks are dirty for partial copying. dirty_blocks_mask |= (1u << (i >> 6)); diff --git a/src/xenia/gpu/shared_memory.h b/src/xenia/gpu/shared_memory.h index 2f0cd9c2b..340d60abf 100644 --- a/src/xenia/gpu/shared_memory.h +++ b/src/xenia/gpu/shared_memory.h @@ -75,13 +75,12 @@ class SharedMemory { // Checks if the range has been updated, uploads new data if needed and // ensures the host GPU memory backing the range are resident. Returns true if // the range has been fully updated and is usable. - bool RequestRange(uint32_t start, uint32_t length, - bool* any_data_resolved_out = nullptr); + bool RequestRange(uint32_t start, uint32_t length); void TryFindUploadRange(const uint32_t& block_first, const uint32_t& block_last, const uint32_t& page_first, const uint32_t& page_last, - bool& any_data_resolved, uint32_t& range_start, + uint32_t& range_start, unsigned int& current_upload_range, std::pair* uploads); @@ -104,7 +103,7 @@ class SharedMemory { // be called, to make sure, if the GPU writes don't overwrite *everything* in // the pages they touch, the CPU data is properly loaded to the unmodified // regions in those pages. - void RangeWrittenByGpu(uint32_t start, uint32_t length, bool is_resolve); + void RangeWrittenByGpu(uint32_t start, uint32_t length); protected: SharedMemory(Memory& memory); @@ -138,8 +137,7 @@ class SharedMemory { uint32_t length_allocations); // Mark the memory range as updated and protect it. - void MakeRangeValid(uint32_t start, uint32_t length, bool written_by_gpu, - bool written_by_gpu_resolve); + void MakeRangeValid(uint32_t start, uint32_t length, bool written_by_gpu); // Uploads a range of host pages - only called if host GPU sparse memory // allocation succeeded if needed. While uploading, MakeRangeValid must be @@ -208,9 +206,6 @@ class SharedMemory { // Subset of valid pages - whether each page in the GPU buffer contains data // that was written on the GPU, thus should not be invalidated spuriously. uint64_t valid_and_gpu_written; - // Subset of valid_and_gpu_written - whether each page in the GPU buffer - // contains data written specifically by resolving from EDRAM. - uint64_t valid_and_gpu_resolved; }; // chrispy: todo, systempageflagsblock should be 3 different arrays @@ -236,8 +231,7 @@ class SharedMemory { // this reduces copy overhead by 80-95%. std::atomic dirty_blocks_{0}; - uint64_t *system_page_flags_valid_and_gpu_written_ = nullptr, - *system_page_flags_valid_and_gpu_resolved_ = nullptr; + uint64_t* system_page_flags_valid_and_gpu_written_ = nullptr; unsigned num_system_page_flags_ = 0; static std::pair MemoryInvalidationCallbackThunk( void* context_ptr, uint32_t physical_address_start, uint32_t length, diff --git a/src/xenia/gpu/spirv_shader_translator.h b/src/xenia/gpu/spirv_shader_translator.h index e28fb1a4e..6f47c9b11 100644 --- a/src/xenia/gpu/spirv_shader_translator.h +++ b/src/xenia/gpu/spirv_shader_translator.h @@ -397,7 +397,6 @@ class SpirvShaderTranslator : public ShaderTranslator { SpirvShaderTranslator( const Features& features, bool native_2x_msaa_with_attachments, bool native_2x_msaa_no_attachments, bool edram_fragment_shader_interlock, - bool gamma_render_target_as_srgb = false, uint32_t draw_resolution_scale_x = 1, uint32_t draw_resolution_scale_y = 1, ui::vulkan::SpirvToolsContext* spirv_tools_context = nullptr, @@ -406,7 +405,6 @@ class SpirvShaderTranslator : public ShaderTranslator { native_2x_msaa_with_attachments_(native_2x_msaa_with_attachments), native_2x_msaa_no_attachments_(native_2x_msaa_no_attachments), edram_fragment_shader_interlock_(edram_fragment_shader_interlock), - gamma_render_target_as_srgb_(gamma_render_target_as_srgb), draw_resolution_scale_x_(draw_resolution_scale_x), draw_resolution_scale_y_(draw_resolution_scale_y), spirv_tools_context_(spirv_tools_context), @@ -796,9 +794,6 @@ class SpirvShaderTranslator : public ShaderTranslator { // flow of the main function, and that there are no returns before either // (there's a single return from the shader). bool edram_fragment_shader_interlock_; - // Whether with host render targets, k_8_8_8_8_GAMMA render targets are - // represented as host sRGB (gamma applied by the host after blending). - bool gamma_render_target_as_srgb_; // Is currently writing the empty depth-only pixel shader, such as for depth // and stencil testing with fragment shader interlock. diff --git a/src/xenia/gpu/spirv_shader_translator_fetch.cc b/src/xenia/gpu/spirv_shader_translator_fetch.cc index f3be00adf..dd79853ef 100644 --- a/src/xenia/gpu/spirv_shader_translator_fetch.cc +++ b/src/xenia/gpu/spirv_shader_translator_fetch.cc @@ -2342,7 +2342,6 @@ void SpirvShaderTranslator::ProcessTextureFetchInstruction( builder_->createBranch(&block_sign_merge); // Gamma. builder_->setBuildPoint(&block_sign_gamma_start); - // TODO(Triang3l): Gamma resolve target as sRGB sampling. spv::Id sample_result_component_gamma = PWLGammaToLinear(sample_result_component_unsigned, false); // Get the current build point for the phi operation not to assume diff --git a/src/xenia/gpu/spirv_shader_translator_rb.cc b/src/xenia/gpu/spirv_shader_translator_rb.cc index cc600dc8a..6a6cb9aa9 100644 --- a/src/xenia/gpu/spirv_shader_translator_rb.cc +++ b/src/xenia/gpu/spirv_shader_translator_rb.cc @@ -1251,45 +1251,41 @@ void SpirvShaderTranslator::CompleteFragmentShaderInMain() { if_rt_write_mask_not_empty.makeEndIf(); if_fsi_color_written.makeEndIf(); } else { - if (!gamma_render_target_as_srgb_) { - // Convert to gamma space - this is incorrect, since it must be done - // after blending on the Xbox 360, but this is just one of many - // blending issues in the host render target path. When - // gamma_render_target_as_srgb_ is enabled, the host handles gamma - // conversion via sRGB render target format after blending. - uint_vector_temp_.clear(); - uint_vector_temp_.push_back(0); - uint_vector_temp_.push_back(1); - uint_vector_temp_.push_back(2); - spv::Id color_rgb = builder_->createRvalueSwizzle( - spv::NoPrecision, type_float3_, color, uint_vector_temp_); - spv::Id is_gamma = builder_->createBinOp( - spv::OpINotEqual, type_bool_, - builder_->createBinOp( - spv::OpBitwiseAnd, type_uint_, main_system_constant_flags_, - builder_->makeUintConstant(kSysFlag_ConvertColor0ToGamma - << color_target_index)), - const_uint_0_); - SpirvBuilder::IfBuilder if_gamma( - is_gamma, spv::SelectionControlDontFlattenMask, *builder_); - spv::Id color_rgb_gamma = LinearToPWLGamma(color_rgb, false); - if_gamma.makeEndIf(); - color_rgb = if_gamma.createMergePhi(color_rgb_gamma, color_rgb); - { - std::unique_ptr color_rgba_shuffle_op = - std::make_unique(builder_->getUniqueId(), - type_float4_, - spv::OpVectorShuffle); - color_rgba_shuffle_op->addIdOperand(color_rgb); - color_rgba_shuffle_op->addIdOperand(color); - color_rgba_shuffle_op->addImmediateOperand(0); - color_rgba_shuffle_op->addImmediateOperand(1); - color_rgba_shuffle_op->addImmediateOperand(2); - color_rgba_shuffle_op->addImmediateOperand(3 + 3); - color = color_rgba_shuffle_op->getResultId(); - builder_->getBuildPoint()->addInstruction( - std::move(color_rgba_shuffle_op)); - } + // Convert to gamma space - this is incorrect, since it must be done + // after blending on the Xbox 360, but this is just one of many blending + // issues in the host render target path. + // TODO(Triang3l): Gamma as unorm8 check. + uint_vector_temp_.clear(); + uint_vector_temp_.push_back(0); + uint_vector_temp_.push_back(1); + uint_vector_temp_.push_back(2); + spv::Id color_rgb = builder_->createRvalueSwizzle( + spv::NoPrecision, type_float3_, color, uint_vector_temp_); + spv::Id is_gamma = builder_->createBinOp( + spv::OpINotEqual, type_bool_, + builder_->createBinOp( + spv::OpBitwiseAnd, type_uint_, main_system_constant_flags_, + builder_->makeUintConstant(kSysFlag_ConvertColor0ToGamma + << color_target_index)), + const_uint_0_); + SpirvBuilder::IfBuilder if_gamma( + is_gamma, spv::SelectionControlDontFlattenMask, *builder_); + spv::Id color_rgb_gamma = LinearToPWLGamma(color_rgb, false); + if_gamma.makeEndIf(); + color_rgb = if_gamma.createMergePhi(color_rgb_gamma, color_rgb); + { + std::unique_ptr color_rgba_shuffle_op = + std::make_unique( + builder_->getUniqueId(), type_float4_, spv::OpVectorShuffle); + color_rgba_shuffle_op->addIdOperand(color_rgb); + color_rgba_shuffle_op->addIdOperand(color); + color_rgba_shuffle_op->addImmediateOperand(0); + color_rgba_shuffle_op->addImmediateOperand(1); + color_rgba_shuffle_op->addImmediateOperand(2); + color_rgba_shuffle_op->addImmediateOperand(3 + 3); + color = color_rgba_shuffle_op->getResultId(); + builder_->getBuildPoint()->addInstruction( + std::move(color_rgba_shuffle_op)); } builder_->createStore(color, color_variable); diff --git a/src/xenia/gpu/texture_address.h b/src/xenia/gpu/texture_address.h index 5f1c49d37..8b36854be 100644 --- a/src/xenia/gpu/texture_address.h +++ b/src/xenia/gpu/texture_address.h @@ -37,19 +37,12 @@ namespace texture_address { // must not access the data in the padding after aligning to 32x32 // (resulting in 1280x736) via the guest CPU memory mappings. -// "Element" refers to a storage unit used in guest addressing calculations: -// block for compressed or otherwise block-based formats, pixel for other -// formats. -// TODO(Triang3l): Research 32_32_32_FLOAT format ("Expand3x" in AddrLib terms) -// texture storage. Should storage dimensions be rounded assuming that one -// element is one component rather than the whole pixel? - // Level pitch (or width for non-base mip levels), height, and depth (for 3D // textures - stacked 2D textures use the exact array layer count for the stride // at every level, and cubemaps are normally stored with a stride of 6 faces) // are rounded up by the hardware for the purposes of calculating strides of // subresources and of macro tiles within a subresource to: -// - 32x32 (2D) or 32x32x4 (3D) elements. This is applicable to both tiled and +// - 32x32 (2D) or 32x32x4 (3D) blocks. This is applicable to both tiled and // linear textures. Note that for 3D, this is larger than a macro tile // (32x16x4). // - Power of two, for non-base mip levels. @@ -69,12 +62,12 @@ namespace texture_address { // Another scenario where the product of the aligned dimensions may not be an // accurate estimate (but in this case too small rather than too large) is when // the width exceeds the pitch. -constexpr unsigned int kStoragePitchHeightAlignmentElementsLog2 = 5; -constexpr uint32_t kStoragePitchHeightAlignmentElements = - uint32_t(1) << kStoragePitchHeightAlignmentElementsLog2; -constexpr unsigned int kStorageDepthAlignmentElementsLog2 = 2; -constexpr uint32_t kStorageDepthAlignmentElements = - uint32_t(1) << kStorageDepthAlignmentElementsLog2; +constexpr unsigned int kStoragePitchHeightAlignmentBlocksLog2 = 5; +constexpr uint32_t kStoragePitchHeightAlignmentBlocks = + uint32_t(1) << kStoragePitchHeightAlignmentBlocksLog2; +constexpr unsigned int kStorageDepthAlignmentBlocksLog2 = 2; +constexpr uint32_t kStorageDepthAlignmentBlocks = + uint32_t(1) << kStorageDepthAlignmentBlocksLog2; // 2D tiling from: // https://github.com/BinomialLLC/crunch/blob/36479bc697be19168daafbf15f47f3c60ccec004/inc/crn_decomp.h#L4107 @@ -105,31 +98,31 @@ constexpr uint32_t kStorageDepthAlignmentElements = // [4] = `y & 1` (Y least significant bit) // [3:0] = `outer_inner_bytes[3:0]` (pipe interleave lower bits) // -// The "outer_inner" part is scaled by the number of bytes per element: -// `outer_inner_bytes = outer_inner_elements << log2(bytes_per_element)` -// where `outer_inner_elements` is a sum (or bitwise OR) of two terms: -// - `outer_elements`: Origin of the 32x32 (2D) or 32x16x4 (3D) macro tile -// within the subresource. -// Macro tiles are treated as laid out block-linearly in the calculation of -// this part, with Z being the most significant term, Y in between, and X +// The "outer_inner" part is scaled by the number of bytes per block: +// `outer_inner_bytes = outer_inner_blocks << log2(bytes_per_block)` +// where `outer_inner_blocks` is a sum (or bitwise OR) of two terms: +// - `outer_blocks`: Origin of the 32x32 (2D) or 32x16x4 (3D) macro tile within +// the subresource. +// Macro tiles are treated as laid out macro-tile-linearly in the calculation +// of this part, with Z being the most significant term, Y in between, and X // being the least significant. // 2D: // (x[:5] + ceil(pitch / 32) * y[:5]) << 6 // 3D: // (x[:5] + ceil(pitch / 32) * (y[:4] + 2 * ceil(height / 32) * z[:2])) << 7 -// - `inner_elements`: Offset of 1x2x1 elements within a 8x16 (2D) or 8x8x4 -// micro tile. +// - `inner_blocks`: Offset of 1x2x1 blocks within a 8x16 (2D) or 8x8x4 micro +// tile. // Though the LSB of Y always goes to the bit 4 of the memory address, within // a micro tile, {X, Y[:1], Z} can be treated as linear in the calculation of // this part, with Z being the most significant, Y in between, and X being the // least significant. // 2D: -// - inner_elements[5:3] = y[3:1] -// - inner_elements[2:0] = x[2:0] +// - inner_blocks[5:3] = y[3:1] +// - inner_blocks[2:0] = x[2:0] // 3D: -// - inner_elements[6:5] = z[1:0] -// - inner_elements[4:3] = y[2:1] -// - inner_elements[2:0] = x[2:0] +// - inner_blocks[6:5] = z[1:0] +// - inner_blocks[4:3] = y[2:1] +// - inner_blocks[2:0] = x[2:0] // // However, the "outer/inner" part doesn't include the offset of the micro tile // within the macro tile: X[4:3], and Y[4] (2D) or Y[3] (3D). These are encoded @@ -191,54 +184,54 @@ Address TiledCombine(const Address outer_inner_bytes, const uint32_t bank, } // The absolute of the return value is below 2^31 (for 16384x8192 with 16 bytes -// per element, though it's not even clear if the hardware allows pitches -// greater than 8192, the maximum texture size, but the pitch field in a texture -// fetch constant is 14 bits wide). +// per block, though it's not even clear if the hardware allows pitches greater +// than 8192, the maximum texture size, but the pitch field in a texture fetch +// constant is 14 bits wide). inline int32_t Tiled2D(const int32_t x, const int32_t y, const uint32_t pitch_aligned, - const unsigned int bytes_per_element_log2) { + const unsigned int bytes_per_block_log2) { // Expecting that all the needed rounding (not only to 32x32, but also to a // power of two for mips) is done before the call so this function works with // the actual storage dimensions. - assert_zero(pitch_aligned & (kStoragePitchHeightAlignmentElements - 1)); - const int32_t outer_elements = + assert_zero(pitch_aligned & (kStoragePitchHeightAlignmentBlocks - 1)); + const int32_t outer_blocks = ((y >> kMacroTileHeight2DLog2) * int32_t(pitch_aligned >> kMacroTileWidthLog2) + (x >> kMacroTileWidthLog2)) << 6; - const int32_t inner_elements = (((y >> 1) & 0b111) << 3) | (x & 0b111); - const int32_t outer_inner_bytes = (outer_elements | inner_elements) - << bytes_per_element_log2; + const int32_t inner_blocks = (((y >> 1) & 0b111) << 3) | (x & 0b111); + const int32_t outer_inner_bytes = (outer_blocks | inner_blocks) + << bytes_per_block_log2; const uint32_t bank = (y >> 4) & 0b1; const uint32_t pipe = ((x >> 3) & 0b11) ^ (((y >> 3) & 0b1) << 1); return TiledCombine(outer_inner_bytes, bank, pipe, y & 1); } // The absolute of the return value is below 2^39 (for 16384x2048x1024 with 16 -// bytes per element). This also means that 32 (more precisely, 27) bits is -// always enough for the page index within a subresource. +// bytes per block). This also means that 32 (more precisely, 27) bits is always +// enough for the page index within a subresource. inline int64_t Tiled3D(const int32_t x, const int32_t y, const int32_t z, const uint32_t pitch_aligned, const uint32_t height_aligned, - const unsigned int bytes_per_element_log2) { + const unsigned int bytes_per_block_log2) { // Expecting that all the needed rounding (not only to 32x32x4, but also to a // power of two for mips) is done before the call so this function works with // the actual storage dimensions. - assert_zero(pitch_aligned & (kStoragePitchHeightAlignmentElements - 1)); - assert_zero(height_aligned & (kStoragePitchHeightAlignmentElements - 1)); - // The absolute of `outer_elements` is below (1024 / 4) * (2048 / 16) * + assert_zero(pitch_aligned & (kStoragePitchHeightAlignmentBlocks - 1)); + assert_zero(height_aligned & (kStoragePitchHeightAlignmentBlocks - 1)); + // The absolute of `outer_blocks` is below (1024 / 4) * (2048 / 16) * // (16384 / 32) * 128 = 2^31. - const int32_t outer_elements = + const int32_t outer_blocks = ((((z >> kMacroTileDepthLog2) * (height_aligned >> kMacroTileHeight3DLog2) + (y >> kMacroTileHeight3DLog2)) * int32_t(pitch_aligned >> kMacroTileWidthLog2)) + (x >> kMacroTileWidthLog2)) << 7; - const int32_t inner_elements = + const int32_t inner_blocks = ((z & 0b11) << 5) | (((y >> 1) & 0b11) << 3) | (x & 0b111); - const int64_t outer_inner_bytes = int64_t(outer_elements | inner_elements) - << bytes_per_element_log2; + const int64_t outer_inner_bytes = int64_t(outer_blocks | inner_blocks) + << bytes_per_block_log2; const uint32_t bank = ((y >> 3) ^ (z >> 2)) & 0b1; const uint32_t pipe = ((x >> 3) & 0b11) ^ (bank << 1); return TiledCombine(outer_inner_bytes, bank, pipe, y & 1); diff --git a/src/xenia/gpu/texture_cache.cc b/src/xenia/gpu/texture_cache.cc index b50a8986e..0531e2cf3 100644 --- a/src/xenia/gpu/texture_cache.cc +++ b/src/xenia/gpu/texture_cache.cc @@ -329,7 +329,7 @@ void TextureCache::MarkRangeAsResolved(uint32_t start_unscaled, // Invalidate textures. Toggling individual textures between scaled and // unscaled also relies on invalidation through shared memory. - shared_memory().RangeWrittenByGpu(start_unscaled, length_unscaled, true); + shared_memory().RangeWrittenByGpu(start_unscaled, length_unscaled); } uint32_t TextureCache::GuestToHostSwizzle(uint32_t guest_swizzle, @@ -508,8 +508,6 @@ TextureCache::Texture::Texture(TextureCache& texture_cache, : texture_cache_(texture_cache), key_(key), guest_layout_(key.GetGuestLayout()), - base_resolved_(key.scaled_resolve), - mips_resolved_(key.scaled_resolve), last_usage_submission_index_(texture_cache.current_submission_index_), last_usage_time_(texture_cache.current_submission_time_), used_previous_(track_usage ? texture_cache.texture_used_last_ : nullptr), @@ -767,21 +765,17 @@ void TextureCache::LoadTexturesData(Texture** textures, uint32_t n_textures) { // portion of its pages is invalidated, in this case we'll need the texture // from the shared memory to load the unscaled parts. // TODO(Triang3l): Load unscaled parts. - bool base_resolved = texture.GetBaseResolved(); if (index_base_outdated & (1ULL << i)) { if (!shared_memory().RequestRange( texture_key.base_page << 12, - xe::align(texture.GetGuestBaseSize(), UINT32_C(16)), - texture_key.scaled_resolve ? nullptr : &base_resolved)) { + xe::align(texture.GetGuestBaseSize(), UINT32_C(16)))) { continue; } } - bool mips_resolved = texture.GetMipsResolved(); if (index_mips_outdated & (1ULL << i)) { if (!shared_memory().RequestRange( texture_key.mip_page << 12, - xe::align(texture.GetGuestMipsSize(), UINT32_C(16)), - texture_key.scaled_resolve ? nullptr : &mips_resolved)) { + xe::align(texture.GetGuestMipsSize(), UINT32_C(16)))) { continue; } } @@ -807,13 +801,6 @@ void TextureCache::LoadTexturesData(Texture** textures, uint32_t n_textures) { continue; } - // Update the source of the texture (resolve vs. CPU or memexport) for - // purposes of handling piecewise gamma emulation via sRGB and for - // resolution scale in sampling offsets. - if (!texture_key.scaled_resolve) { - texture.SetBaseResolved(base_resolved); - texture.SetMipsResolved(mips_resolved); - } // reque for makeuptodatandwatch textures[i] = &texture; } @@ -870,21 +857,17 @@ bool TextureCache::LoadTextureData(Texture& texture) { // its pages is invalidated, in this case we'll need the texture from the // shared memory to load the unscaled parts. // TODO(Triang3l): Load unscaled parts. - bool base_resolved = texture.GetBaseResolved(); if (base_outdated) { if (!shared_memory().RequestRange( texture_key.base_page << 12, - xe::align(texture.GetGuestBaseSize(), UINT32_C(16)), - texture_key.scaled_resolve ? nullptr : &base_resolved)) { + xe::align(texture.GetGuestBaseSize(), UINT32_C(16)))) { return false; } } - bool mips_resolved = texture.GetMipsResolved(); if (mips_outdated) { if (!shared_memory().RequestRange( texture_key.mip_page << 12, - xe::align(texture.GetGuestMipsSize(), UINT32_C(16)), - texture_key.scaled_resolve ? nullptr : &mips_resolved)) { + xe::align(texture.GetGuestMipsSize(), UINT32_C(16)))) { return false; } } @@ -909,14 +892,6 @@ bool TextureCache::LoadTextureData(Texture& texture) { return false; } - // Update the source of the texture (resolve vs. CPU or memexport) for - // purposes of handling piecewise gamma emulation via sRGB and for resolution - // scale in sampling offsets. - if (!texture_key.scaled_resolve) { - texture.SetBaseResolved(base_resolved); - texture.SetMipsResolved(mips_resolved); - } - // Mark the ranges as uploaded and watch them. This is needed for scaled // resolves as well to detect when the CPU wants to reuse the memory for a // regular texture or a vertex buffer, and thus the scaled resolve version is diff --git a/src/xenia/gpu/texture_cache.h b/src/xenia/gpu/texture_cache.h index 131fadca4..a87656bb7 100644 --- a/src/xenia/gpu/texture_cache.h +++ b/src/xenia/gpu/texture_cache.h @@ -137,7 +137,7 @@ class TextureCache { GetValidTextureBinding(fetch_constant_index); return binding ? binding->swizzled_signs : kSwizzledSignsUnsigned; } - bool IsActiveTextureResolved(uint32_t fetch_constant_index) const { + bool IsActiveTextureResolutionScaled(uint32_t fetch_constant_index) const { const TextureBinding* binding = GetValidTextureBinding(fetch_constant_index); if (!binding) { @@ -277,18 +277,6 @@ class TextureCache { } uint64_t last_usage_time() const { return last_usage_time_; } - bool GetBaseResolved() const { return base_resolved_; } - void SetBaseResolved(bool base_resolved) { - assert_false(!base_resolved && key().scaled_resolve); - base_resolved_ = base_resolved; - } - bool GetMipsResolved() const { return mips_resolved_; } - void SetMipsResolved(bool mips_resolved) { - assert_false(!mips_resolved && key().scaled_resolve); - mips_resolved_ = mips_resolved; - } - bool IsResolved() const { return base_resolved_ || mips_resolved_; } - bool base_outdated(const global_unique_lock_type& global_lock) const { return base_outdated_; } @@ -346,13 +334,6 @@ class TextureCache { // texture is 2D. bool force_load_3d_tiling_ = false; - // Whether the most up-to-date base / mips contain pages with data from a - // resolve operation (rather than from the CPU or memexport), primarily for - // choosing between piecewise linear gamma and sRGB when the former is - // emulated with the latter. - bool base_resolved_; - bool mips_resolved_; - // These are to be accessed within the global critical region to synchronize // with shared memory. // Whether the recent base level data needs reloading from the memory. diff --git a/src/xenia/gpu/vulkan/vulkan_command_processor.cc b/src/xenia/gpu/vulkan/vulkan_command_processor.cc index 033d4dfa2..499de920a 100644 --- a/src/xenia/gpu/vulkan/vulkan_command_processor.cc +++ b/src/xenia/gpu/vulkan/vulkan_command_processor.cc @@ -3551,7 +3551,7 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type, // Invalidate textures in memexported memory and watch for changes. for (const draw_util::MemExportRange& memexport_range : memexport_ranges_) { shared_memory_->RangeWrittenByGpu(memexport_range.base_address_dwords << 2, - memexport_range.size_bytes, false); + memexport_range.size_bytes); } // CPU readback for memexport data (if enabled). @@ -5944,11 +5944,13 @@ void VulkanCommandProcessor::UpdateSystemConstantValues( flags |= uint32_t(alpha_test_function) << SpirvShaderTranslator::kSysFlag_AlphaPassIfLess_Shift; // Gamma writing. - // TODO(Triang3l): Gamma as sRGB check. - for (uint32_t i = 0; i < xenos::kMaxColorRenderTargets; ++i) { - if (color_infos[i].color_format == - xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA) { - flags |= SpirvShaderTranslator::kSysFlag_ConvertColor0ToGamma << i; + // TODO(Triang3l): Gamma as unorm8 check. + if (!edram_fragment_shader_interlock) { + for (uint32_t i = 0; i < xenos::kMaxColorRenderTargets; ++i) { + if (color_infos[i].color_format == + xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA) { + flags |= SpirvShaderTranslator::kSysFlag_ConvertColor0ToGamma << i; + } } } if (edram_fragment_shader_interlock && depth_stencil_enabled) { @@ -6121,7 +6123,8 @@ void VulkanCommandProcessor::UpdateSystemConstantValues( } } - // Textures resolved - which textures are from resolve operations (scaled). + // Textures resolution scaled - which textures are from scaled resolve + // operations. { uint32_t textures_resolved = 0; uint32_t textures_remaining = used_texture_mask; @@ -6129,7 +6132,8 @@ void VulkanCommandProcessor::UpdateSystemConstantValues( while (xe::bit_scan_forward(textures_remaining, &texture_index)) { textures_remaining &= ~(UINT32_C(1) << texture_index); textures_resolved |= - uint32_t(texture_cache_->IsActiveTextureResolved(texture_index)) + uint32_t( + texture_cache_->IsActiveTextureResolutionScaled(texture_index)) << texture_index; } dirty |= system_constants_.textures_resolved != textures_resolved; diff --git a/src/xenia/gpu/vulkan/vulkan_pipeline_cache.cc b/src/xenia/gpu/vulkan/vulkan_pipeline_cache.cc index 1e9fa3c9b..fbf30c214 100644 --- a/src/xenia/gpu/vulkan/vulkan_pipeline_cache.cc +++ b/src/xenia/gpu/vulkan/vulkan_pipeline_cache.cc @@ -113,7 +113,6 @@ bool VulkanPipelineCache::Initialize() { render_target_cache_.msaa_2x_attachments_supported(), render_target_cache_.msaa_2x_no_attachments_supported(), edram_fragment_shader_interlock, - render_target_cache_.gamma_render_target_as_srgb(), render_target_cache_.draw_resolution_scale_x(), render_target_cache_.draw_resolution_scale_y(), nullptr, false); // Never optimize during initial translation @@ -1013,38 +1012,36 @@ void VulkanPipelineCache::TranslateShadersForStorage( render_target_cache_.msaa_2x_attachments_supported(); bool msaa_2x_no_attachments = render_target_cache_.msaa_2x_no_attachments_supported(); - bool gamma_as_srgb = render_target_cache_.gamma_render_target_as_srgb(); uint32_t draw_res_x = render_target_cache_.draw_resolution_scale_x(); uint32_t draw_res_y = render_target_cache_.draw_resolution_scale_y(); - auto translate_function = - [this, &translations_to_do, &translation_index, &translations_completed, - vulkan_device, msaa_2x_attachments, msaa_2x_no_attachments, - edram_fsi_used, gamma_as_srgb, draw_res_x, draw_res_y]() { - // Each thread needs its own translator. - SpirvShaderTranslator translator( - SpirvShaderTranslator::Features(vulkan_device), msaa_2x_attachments, - msaa_2x_no_attachments, edram_fsi_used, gamma_as_srgb, draw_res_x, - draw_res_y, nullptr, - false); // Don't optimize during parallel translation + auto translate_function = [this, &translations_to_do, &translation_index, + &translations_completed, vulkan_device, + msaa_2x_attachments, msaa_2x_no_attachments, + edram_fsi_used, draw_res_x, draw_res_y]() { + // Each thread needs its own translator. + SpirvShaderTranslator translator( + SpirvShaderTranslator::Features(vulkan_device), msaa_2x_attachments, + msaa_2x_no_attachments, edram_fsi_used, draw_res_x, draw_res_y, nullptr, + false); // Don't optimize during parallel translation - while (true) { - size_t index = translation_index.fetch_add(1); - if (index >= translations_to_do.size()) { - break; - } - VulkanShader* shader = translations_to_do[index].first; - uint64_t modification = translations_to_do[index].second; - VulkanShader::VulkanTranslation* translation = - static_cast( - shader->GetTranslation(modification)); - if (translation && !translation->is_translated()) { - if (TranslateAnalyzedShader(translator, *translation)) { - translations_completed.fetch_add(1); - } - } + while (true) { + size_t index = translation_index.fetch_add(1); + if (index >= translations_to_do.size()) { + break; + } + VulkanShader* shader = translations_to_do[index].first; + uint64_t modification = translations_to_do[index].second; + VulkanShader::VulkanTranslation* translation = + static_cast( + shader->GetTranslation(modification)); + if (translation && !translation->is_translated()) { + if (TranslateAnalyzedShader(translator, *translation)) { + translations_completed.fetch_add(1); } - }; + } + } + }; size_t thread_count = 0; if (cvars::vulkan_pipeline_creation_threads != 0) { diff --git a/src/xenia/gpu/vulkan/vulkan_render_target_cache.cc b/src/xenia/gpu/vulkan/vulkan_render_target_cache.cc index cd590eede..de7c0deac 100644 --- a/src/xenia/gpu/vulkan/vulkan_render_target_cache.cc +++ b/src/xenia/gpu/vulkan/vulkan_render_target_cache.cc @@ -507,8 +507,13 @@ bool VulkanRenderTargetCache::Initialize(uint32_t shared_memory_binding_count) { if (path_ == Path::kHostRenderTargets) { // Host render targets. + // TODO(Triang3l): When color space conversion is implemented in the + // ownership transfer and resolve dump shaders, allow + // `gamma_render_target_as_unorm16` if VK_FORMAT_R16G16B16A16_UNORM supports + // the SAMPLED_IMAGE | COLOR_ATTACHMENT | COLOR_ATTACHMENT_BLEND features. + gamma_render_target_as_unorm16_ = false; + depth_float24_round_ = cvars::depth_float24_round; - gamma_render_target_as_srgb_ = cvars::gamma_render_target_as_srgb; // Host depth storing pipeline layout. VkDescriptorSetLayout host_depth_store_descriptor_set_layouts[] = { @@ -712,8 +717,8 @@ bool VulkanRenderTargetCache::Initialize(uint32_t shared_memory_binding_count) { } else if (path_ == Path::kPixelShaderInterlock) { // Pixel (fragment) shader interlock. - // Blending is done in linear space directly in shaders. - gamma_render_target_as_srgb_ = false; + // Piecewise linear gamma is 8-bit with programmable blending. + gamma_render_target_as_unorm16_ = false; // Always true float24 depth rounded to the nearest even. depth_float24_round_ = true; @@ -1417,11 +1422,6 @@ bool VulkanRenderTargetCache::Update( depth_and_color_render_targets, last_update_transfers()); - uint32_t render_targets_are_srgb = - gamma_render_target_as_srgb_ - ? last_update_accumulated_color_targets_are_gamma() - : 0; - if (depth_and_color_render_targets[0]) { render_pass_key.depth_and_color_used |= 1 << 0; render_pass_key.depth_format = @@ -1430,30 +1430,22 @@ bool VulkanRenderTargetCache::Update( if (depth_and_color_render_targets[1]) { render_pass_key.depth_and_color_used |= 1 << 1; render_pass_key.color_0_view_format = - (render_targets_are_srgb & (1 << 0)) - ? xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA - : depth_and_color_render_targets[1]->key().GetColorFormat(); + depth_and_color_render_targets[1]->key().GetColorFormat(); } if (depth_and_color_render_targets[2]) { render_pass_key.depth_and_color_used |= 1 << 2; render_pass_key.color_1_view_format = - (render_targets_are_srgb & (1 << 1)) - ? xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA - : depth_and_color_render_targets[2]->key().GetColorFormat(); + depth_and_color_render_targets[2]->key().GetColorFormat(); } if (depth_and_color_render_targets[3]) { render_pass_key.depth_and_color_used |= 1 << 3; render_pass_key.color_2_view_format = - (render_targets_are_srgb & (1 << 2)) - ? xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA - : depth_and_color_render_targets[3]->key().GetColorFormat(); + depth_and_color_render_targets[3]->key().GetColorFormat(); } if (depth_and_color_render_targets[4]) { render_pass_key.depth_and_color_used |= 1 << 4; render_pass_key.color_3_view_format = - (render_targets_are_srgb & (1 << 3)) - ? xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA - : depth_and_color_render_targets[4]->key().GetColorFormat(); + depth_and_color_render_targets[4]->key().GetColorFormat(); } const Framebuffer* framebuffer = last_update_framebuffer_; @@ -1756,8 +1748,8 @@ VkFormat VulkanRenderTargetCache::GetColorVulkanFormat( case xenos::ColorRenderTargetFormat::k_8_8_8_8: return VK_FORMAT_R8G8B8A8_UNORM; case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA: - return gamma_render_target_as_srgb_ ? VK_FORMAT_R8G8B8A8_SRGB - : VK_FORMAT_R8G8B8A8_UNORM; + return gamma_render_target_as_unorm16_ ? VK_FORMAT_R16G16B16A16_UNORM + : VK_FORMAT_R8G8B8A8_UNORM; case xenos::ColorRenderTargetFormat::k_2_10_10_10: case xenos::ColorRenderTargetFormat::k_2_10_10_10_AS_10_10_10_10: return VK_FORMAT_A2B10G10R10_UNORM_PACK32; @@ -1828,9 +1820,6 @@ VulkanRenderTargetCache::VulkanRenderTarget::~VulkanRenderTarget() { if (view_color_transfer_separate_ != VK_NULL_HANDLE) { dfn.vkDestroyImageView(device, view_color_transfer_separate_, nullptr); } - if (view_srgb_ != VK_NULL_HANDLE) { - dfn.vkDestroyImageView(device, view_srgb_, nullptr); - } if (view_stencil_ != VK_NULL_HANDLE) { dfn.vkDestroyImageView(device, view_stencil_, nullptr); } @@ -1842,6 +1831,10 @@ VulkanRenderTargetCache::VulkanRenderTarget::~VulkanRenderTarget() { dfn.vkFreeMemory(device, memory_, nullptr); } +bool VulkanRenderTargetCache::IsGammaFormatHostStorageSeparate() const { + return gamma_render_target_as_unorm16_; +} + uint32_t VulkanRenderTargetCache::GetMaxRenderTargetWidth() const { const ui::vulkan::VulkanDevice::Properties& device_properties = command_processor_.GetVulkanDevice()->properties(); @@ -1891,7 +1884,6 @@ RenderTargetCache::RenderTarget* VulkanRenderTargetCache::CreateRenderTarget( image_create_info.pQueueFamilyIndices = nullptr; image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; VkFormat transfer_format; - bool is_srgb_view_needed = false; if (key.is_depth) { image_create_info.format = GetDepthVulkanFormat(key.GetDepthFormat()); transfer_format = image_create_info.format; @@ -1900,11 +1892,7 @@ RenderTargetCache::RenderTarget* VulkanRenderTargetCache::CreateRenderTarget( xenos::ColorRenderTargetFormat color_format = key.GetColorFormat(); image_create_info.format = GetColorVulkanFormat(color_format); transfer_format = GetColorOwnershipTransferVulkanFormat(color_format); - is_srgb_view_needed = - gamma_render_target_as_srgb_ && - (color_format == xenos::ColorRenderTargetFormat::k_8_8_8_8 || - color_format == xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA); - if (image_create_info.format != transfer_format || is_srgb_view_needed) { + if (image_create_info.format != transfer_format) { image_create_info.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; } image_create_info.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; @@ -1962,7 +1950,6 @@ RenderTargetCache::RenderTarget* VulkanRenderTargetCache::CreateRenderTarget( } VkImageView view_depth_stencil = VK_NULL_HANDLE; VkImageView view_stencil = VK_NULL_HANDLE; - VkImageView view_srgb = VK_NULL_HANDLE; VkImageView view_color_transfer_separate = VK_NULL_HANDLE; if (key.is_depth) { view_create_info.subresourceRange.aspectMask = @@ -1996,22 +1983,6 @@ RenderTargetCache::RenderTarget* VulkanRenderTargetCache::CreateRenderTarget( return nullptr; } } else { - if (is_srgb_view_needed) { - view_create_info.format = VK_FORMAT_R8G8B8A8_SRGB; - if (dfn.vkCreateImageView(device, &view_create_info, nullptr, - &view_srgb) != VK_SUCCESS) { - XELOGE( - "VulkanRenderTarget: Failed to create an sRGB view for a {}x{} " - "{}xMSAA render target", - image_create_info.extent.width, image_create_info.extent.height, - uint32_t(1) << uint32_t(key.msaa_samples), - xenos::GetColorRenderTargetFormatName(key.GetColorFormat())); - dfn.vkDestroyImageView(device, view_depth_color, nullptr); - dfn.vkDestroyImage(device, image, nullptr); - dfn.vkFreeMemory(device, memory, nullptr); - return nullptr; - } - } if (transfer_format != image_create_info.format) { view_create_info.format = transfer_format; if (dfn.vkCreateImageView(device, &view_create_info, nullptr, @@ -2021,9 +1992,6 @@ RenderTargetCache::RenderTarget* VulkanRenderTargetCache::CreateRenderTarget( "{}xMSAA {} render target", image_create_info.extent.width, image_create_info.extent.height, uint32_t(1) << uint32_t(key.msaa_samples), key.GetFormatName()); - if (view_srgb != VK_NULL_HANDLE) { - dfn.vkDestroyImageView(device, view_srgb, nullptr); - } dfn.vkDestroyImageView(device, view_depth_color, nullptr); dfn.vkDestroyImage(device, image, nullptr); dfn.vkFreeMemory(device, memory, nullptr); @@ -2044,9 +2012,6 @@ RenderTargetCache::RenderTarget* VulkanRenderTargetCache::CreateRenderTarget( if (view_color_transfer_separate != VK_NULL_HANDLE) { dfn.vkDestroyImageView(device, view_color_transfer_separate, nullptr); } - if (view_srgb != VK_NULL_HANDLE) { - dfn.vkDestroyImageView(device, view_srgb, nullptr); - } dfn.vkDestroyImageView(device, view_depth_color, nullptr); dfn.vkDestroyImage(device, image, nullptr); dfn.vkFreeMemory(device, memory, nullptr); @@ -2094,7 +2059,7 @@ RenderTargetCache::RenderTarget* VulkanRenderTargetCache::CreateRenderTarget( 0, nullptr); return new VulkanRenderTarget(key, *this, image, memory, view_depth_color, - view_depth_stencil, view_stencil, view_srgb, + view_depth_stencil, view_stencil, view_color_transfer_separate, descriptor_set_index_transfer_source); } diff --git a/src/xenia/gpu/vulkan/vulkan_render_target_cache.h b/src/xenia/gpu/vulkan/vulkan_render_target_cache.h index 81173069a..c7178b3ea 100644 --- a/src/xenia/gpu/vulkan/vulkan_render_target_cache.h +++ b/src/xenia/gpu/vulkan/vulkan_render_target_cache.h @@ -58,7 +58,6 @@ class VulkanRenderTargetCache final : public RenderTargetCache { // even for kD24S8. xenos::DepthRenderTargetFormat depth_format : xenos::kDepthRenderTargetFormatBits; // 8 - // Linear or sRGB included if host sRGB is used. xenos::ColorRenderTargetFormat color_0_view_format : xenos::kColorRenderTargetFormatBits; // 12 xenos::ColorRenderTargetFormat color_1_view_format @@ -165,8 +164,8 @@ class VulkanRenderTargetCache final : public RenderTargetCache { return depth_unorm24_vulkan_format_supported_; } bool depth_float24_round() const { return depth_float24_round_; } - bool gamma_render_target_as_srgb() const { - return gamma_render_target_as_srgb_; + bool gamma_render_target_as_unorm16() const { + return gamma_render_target_as_unorm16_; } bool msaa_2x_attachments_supported() const { @@ -196,6 +195,8 @@ class VulkanRenderTargetCache final : public RenderTargetCache { bool* is_integer_out = nullptr) const; protected: + bool IsGammaFormatHostStorageSeparate() const override; + uint32_t GetMaxRenderTargetWidth() const override; uint32_t GetMaxRenderTargetHeight() const override; @@ -349,7 +350,6 @@ class VulkanRenderTargetCache final : public RenderTargetCache { VkImage image, VkDeviceMemory memory, VkImageView view_depth_color, VkImageView view_depth_stencil, VkImageView view_stencil, - VkImageView view_srgb, VkImageView view_color_transfer_separate, size_t descriptor_set_index_transfer_source) : RenderTarget(key), @@ -359,7 +359,6 @@ class VulkanRenderTargetCache final : public RenderTargetCache { view_depth_color_(view_depth_color), view_depth_stencil_(view_depth_stencil), view_stencil_(view_stencil), - view_srgb_(view_srgb), view_color_transfer_separate_(view_color_transfer_separate), descriptor_set_index_transfer_source_( descriptor_set_index_transfer_source) {} @@ -434,7 +433,6 @@ class VulkanRenderTargetCache final : public RenderTargetCache { // Optional views. VkImageView view_depth_stencil_; VkImageView view_stencil_; - VkImageView view_srgb_; VkImageView view_color_transfer_separate_; // 2 sampled images for depth / stencil, 1 sampled image for color. @@ -879,7 +877,7 @@ class VulkanRenderTargetCache final : public RenderTargetCache { void DumpRenderTargets(uint32_t dump_base, uint32_t dump_row_length_used, uint32_t dump_rows, uint32_t dump_pitch); - bool gamma_render_target_as_srgb_ = false; + bool gamma_render_target_as_unorm16_ = false; bool depth_unorm24_vulkan_format_supported_ = false; bool depth_float24_round_ = false; diff --git a/src/xenia/gpu/vulkan/vulkan_shared_memory.cc b/src/xenia/gpu/vulkan/vulkan_shared_memory.cc index ac0c7bad4..8f720b1c8 100644 --- a/src/xenia/gpu/vulkan/vulkan_shared_memory.cc +++ b/src/xenia/gpu/vulkan/vulkan_shared_memory.cc @@ -437,7 +437,7 @@ bool VulkanSharedMemory::UploadRanges( break; } MakeRangeValid(upload_range_start << page_size_log2(), - uint32_t(upload_buffer_size), false, false); + uint32_t(upload_buffer_size), false); if (upload_buffer_size < (1ULL << 32) && upload_buffer_size > 8192) { memory::vastcpy( diff --git a/src/xenia/ui/d3d12/d3d12_api.h b/src/xenia/ui/d3d12/d3d12_api.h index ffd4934b9..11dd18626 100644 --- a/src/xenia/ui/d3d12/d3d12_api.h +++ b/src/xenia/ui/d3d12/d3d12_api.h @@ -13,9 +13,16 @@ // Must be included before D3D and DXGI for things like NOMINMAX. #include "xenia/base/platform_win.h" +// Include the up to date versions of the headers of DXGI and Direct3D 12 rather +// than the ones from the Windows SDK before other headers that may also +// potentially include their headers. + +#include "xenia/ui/dxgi_include_win.h" + +#include "third_party/DirectX-Headers/include/directx/d3d12.h" +#include "third_party/DirectX-Headers/include/directx/d3d12sdklayers.h" + #include -#include -#include #include #include #include diff --git a/src/xenia/ui/dxgi_include_win.h b/src/xenia/ui/dxgi_include_win.h new file mode 100644 index 000000000..58347b019 --- /dev/null +++ b/src/xenia/ui/dxgi_include_win.h @@ -0,0 +1,23 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2026 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_UI_DXGI_INCLUDE_WIN_H_ +#define XENIA_UI_DXGI_INCLUDE_WIN_H_ + +// Must be included before Windows headers for definitions like NOMINMAX. +#include "xenia/base/platform_win.h" + +// Include the up to date versions of the DXGI header dependencies rather than +// the ones from the Windows SDK before including the DXGI header. +#include "third_party/DirectX-Headers/include/directx/dxgicommon.h" +#include "third_party/DirectX-Headers/include/directx/dxgiformat.h" + +#include + +#endif // XENIA_UI_DXGI_INCLUDE_WIN_H_ diff --git a/src/xenia/ui/presenter.h b/src/xenia/ui/presenter.h index a0b64a829..c1503626b 100644 --- a/src/xenia/ui/presenter.h +++ b/src/xenia/ui/presenter.h @@ -40,7 +40,8 @@ // Windows handle types. #include "xenia/base/platform_win.h" -#include +#include "xenia/ui/dxgi_include_win.h" + #include #endif // XE_PLATFORM diff --git a/third_party/DirectX-Headers b/third_party/DirectX-Headers new file mode 160000 index 000000000..33374754f --- /dev/null +++ b/third_party/DirectX-Headers @@ -0,0 +1 @@ +Subproject commit 33374754f65baac0500dda6187e371136357246f