diff --git a/src/core/hle/service/nx2_gsv.h b/src/core/hle/service/nx2_gsv.h index 8a76bff974..9c98f1248b 100644 --- a/src/core/hle/service/nx2_gsv.h +++ b/src/core/hle/service/nx2_gsv.h @@ -48,6 +48,7 @@ #include "core/hle/kernel/k_memory_manager.h" #include "core/hle/kernel/k_page_group.h" #include "core/hle/kernel/k_resource_limit.h" +#include "core/hle/kernel/k_scoped_resource_reservation.h" #include "core/hle/kernel/k_server_session.h" #include "core/hle/kernel/k_event.h" #include "core/hle/kernel/k_hardware_timer.h" @@ -80,13 +81,22 @@ namespace Service { // works from a domain too, for callers that want a raw session handle. template inline Kernel::KClientSession* CreateRawSession(Core::System& system, HLERequestContext& ctx, - std::shared_ptr iface) { + std::shared_ptr iface, Result& result) { auto& kernel = system.Kernel(); - ASSERT(Kernel::GetCurrentProcess(kernel).GetResourceLimit()->Reserve( - kernel, Kernel::LimitableResource::SessionCountMax, 1)); + Kernel::KScopedResourceReservation reservation{ + kernel, &Kernel::GetCurrentProcess(kernel), Kernel::LimitableResource::SessionCountMax}; + if (!reservation.Succeeded()) { + result = Kernel::ResultLimitReached; + return nullptr; + } auto* session = Kernel::KSession::Create(kernel); + if (session == nullptr) { + result = Kernel::ResultOutOfResource; + return nullptr; + } session->Initialize(kernel, nullptr, 0); + reservation.Commit(); Kernel::KSession::Register(kernel, session); auto manager = @@ -122,7 +132,17 @@ inline void TipcHandshake(HLERequestContext& ctx, std::string_view session) { // Replies with success and a copied handle to a new session served by `iface`. template inline void ReturnRawSession(Core::System& system, HLERequestContext& ctx, std::shared_ptr iface) { - auto* client = CreateRawSession(system, ctx, std::move(iface)); + Result result = ResultSuccess; + auto* client = CreateRawSession(system, ctx, std::move(iface), result); + if (client == nullptr) { + IPC::ResponseBuilder rb{ctx, 2}; + if (ctx.IsTipc()) { + rb.PushRaw(result.raw); + } else { + rb.Push(result); + } + return; + } IPC::ResponseBuilder rb{ctx, 2, 1, 0}; rb.Push(ResultSuccess); rb.PushCopyObjects(ctx, *client); @@ -1462,6 +1482,8 @@ struct Nx2NativeObjects { bool decoder{}; // a video decoder output image (gsv 911) // A decoder image the client reads on the GPU (gsv 428/426): written block-linear. bool gpu_read{}; + bool pitch_frame{}; // holds a frame written before gpu_read was set + std::shared_ptr decoder_mutex; u32 gpu_maps{}; // outstanding 426 maps bool mapped_here{}; // 426 added its GPU mapping (no other mapping covered it) }; @@ -1470,6 +1492,12 @@ struct Nx2NativeObjects { std::map images; u32 next_memory{1}; u32 next_image{1}; + + std::shared_ptr DecoderMutex(u32 id) { + std::scoped_lock lk{mutex}; + const auto image = images.find(id); + return image != images.end() ? image->second.decoder_mutex : nullptr; + } }; // Block-linear layout of an InterModuleUserImagePreference (16 words): word 2 holds @@ -1619,6 +1647,31 @@ inline Nx2DecoderImageLayout Nx2CalculateDecoderImageLayout(const u32* preferenc return layout; } +// Rewrites a decoder image holding a pitch-linear frame in the layout GPU reads use (RGBA, +// native block-linear, 16-GOB blocks). For frames decoded before the client's first 428/426. +inline void Nx2RelayoutDecoderImage(Core::System& system, u64 address, + const Nx2DecoderImageLayout& layout) { + if (layout.format == Tegra::Host1x::Nx2PictureFormat::Nv12 || + system.ApplicationProcess() == nullptr) { + return; + } + auto& memory = system.ApplicationMemory(); + std::vector linear(layout.size); + memory.ReadBlock(address, linear.data(), linear.size()); + if (layout.format == Tegra::Host1x::Nx2PictureFormat::Bgra8) { + for (size_t i = 0; i + 3 < linear.size(); i += 4) { + std::swap(linear[i], linear[i + 2]); + } + } + std::vector tiled(layout.size); + Tegra::Texture::SwizzleTexture(tiled, linear, 4, static_cast(layout.pitch / 4), + layout.height, 1, 4, 0, 1, true); + memory.WriteBlock(address, tiled.data(), tiled.size()); + if (const auto device = Nvidia::Devices::Nx2GpuMemoryRegistry::Get().DeviceAddressOf(address)) { + system.GPU().InvalidateRegion(*device, tiled.size()); + } +} + // Placeholder for the Switch 2 display session (nn::vi2::sdk), handed out by gsv 305. Reply // shapes read from the SDK: TIPC 1 -> u32 < 0x400, TIPC 3 -> u32 < 8, TIPC 11/31(u32) and // 32(handle, u32) -> one handle at word 3 then the result. Handles returned here are events (a @@ -2930,13 +2983,18 @@ class INx2VideoSession final : public ServiceFramework { // Writes a decoded frame into an output image; returns the size the frame reports. std::pair WriteImage(Tegra::Host1x::Nx2VideoDecoder* decoder, u32 image_id, const Decoded& frame, std::vector& bytes) { + const auto image_mutex = m_objects->DecoderMutex(image_id); + if (!image_mutex) { + return {m_width, m_height}; + } + std::scoped_lock image_lock{*image_mutex}; u64 address{}; Nx2DecoderImageLayout layout{}; bool block_linear{}; { std::scoped_lock lk{m_objects->mutex}; const auto image = m_objects->images.find(image_id); - if (image == m_objects->images.end()) { + if (image == m_objects->images.end() || image->second.decoder_mutex != image_mutex) { return {m_width, m_height}; } layout = Nx2CalculateDecoderImageLayout(image->second.preference.data()); @@ -2983,6 +3041,19 @@ class INx2VideoSession final : public ServiceFramework { if (const auto device = Nvidia::Devices::Nx2GpuMemoryRegistry::Get().DeviceAddressOf(address)) { system.GPU().InvalidateRegion(*device, layout.size); } + // A 428/426 that came in while this frame was written didn't see it: relayout it here. + bool relayout = false; + if (layout.format != Tegra::Host1x::Nx2PictureFormat::Nv12) { + std::scoped_lock lk{m_objects->mutex}; + if (const auto image = m_objects->images.find(image_id); + image != m_objects->images.end()) { + relayout = image->second.gpu_read; + image->second.pitch_frame = !relayout; + } + } + if (relayout) { + Nx2RelayoutDecoderImage(system, address, layout); + } return {width, height}; } @@ -3044,6 +3115,32 @@ class INx2VideoSession final : public ServiceFramework { std::thread m_worker; }; +// Session returned by gsv 1121. The SDK sends TIPC 0 (a tick-like u64 it requires echoed) right +// after getting the handle, then 11 with two u64 values. The server behind 11 is unknown, so it +// logs its arguments and replies 0. Left to the zeroed stub, 1121 had no handle and Welcome Tour +// aborted with 2001-0114 during boot. +class INx2Session1121 final : public ServiceFramework { +public: + explicit INx2Session1121(Core::System& system_) : ServiceFramework{system_, "nx2:gsv:1121"} { + RegisterHandlersTipc({ + {0, &INx2Session1121::Handshake, "Handshake"}, + {11, &INx2Session1121::Command11, "Unknown11"}, + }); + } + +private: + void Handshake(HLERequestContext& ctx) { + TipcHandshake(ctx, "nx2:gsv:1121"); + } + + void Command11(HLERequestContext& ctx) { + const auto* w = ctx.CommandBuffer(); + LOG_DEBUG(Service, "(STUBBED) nx2:gsv:1121 TIPC 11 args={:#x}, {:#x}", + u64{w[2]} | (u64{w[3]} << 32), u64{w[4]} | (u64{w[5]} << 32)); + TipcReplyWords(ctx, {0, 0}); + } +}; + // Placeholder for the Switch 2 graphics-system server that nn::gsv::BootWithRemoteMemory talks // to over TIPC. Nothing behind it is emulated: commands succeed and are logged, so boot can show // what comes next. Commands 202 and 1402 hand out the syncpt and nvsrv sessions the SDK uses. @@ -3164,7 +3261,7 @@ class INx2GraphicsSystemSession final : public ServiceFramework(system), result); + if (client == nullptr) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.PushRaw(result.raw); + return; + } + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.PushRaw(ResultSuccess.raw); + rb.PushMoveObjects(ctx, *client); + } + std::set m_address_spaces; ///< Ids attached with 434 bool m_client_handle_registered = false; ///< Set by 11 @@ -3617,16 +3729,23 @@ class INx2GraphicsSystemSession final : public ServiceFrameworkDecoderMutex(handle); + if (!image_mutex) { + return false; + } + std::scoped_lock image_lock{*image_mutex}; const bool map = ctx.GetCommand() == 426; u64 address{}; u64 size{}; u64 offset{}; u32 dimensions{}; bool register_mapping = false; + u64 relayout_address{}; + Nx2DecoderImageLayout relayout_layout{}; { std::scoped_lock lk{m_objects->mutex}; const auto image = m_objects->images.find(handle); - if (image == m_objects->images.end() || !image->second.decoder) { + if (image == m_objects->images.end() || image->second.decoder_mutex != image_mutex) { return false; } const auto memory = m_objects->memories.find(image->second.memory); @@ -3634,6 +3753,10 @@ class INx2GraphicsSystemSession final : public ServiceFrameworksecond.gpu_read = true; + if (std::exchange(image->second.pitch_frame, false)) { + relayout_address = memory->second.address + image->second.offset; + relayout_layout = Nx2CalculateDecoderImageLayout(image->second.preference.data()); + } offset = image->second.offset; size = image->second.size; dimensions = image->second.preference[2]; @@ -3647,6 +3770,10 @@ class INx2GraphicsSystemSession final : public ServiceFramework words{}; @@ -3677,6 +3804,11 @@ class INx2GraphicsSystemSession final : public ServiceFrameworkDecoderMutex(handle); + std::unique_lock image_lock; + if (image_mutex) { + image_lock = std::unique_lock{*image_mutex}; + } std::optional> unmap; { std::scoped_lock lk{m_objects->mutex}; @@ -3729,6 +3861,7 @@ class INx2GraphicsSystemSession final : public ServiceFramework(); } LOG_INFO(Service, "nx2:gsv 911 decoder images {}..{} code={} {}x{} in memory {}", first, first + count - 1, layout.code, layout.width, layout.height, w[19]); @@ -3770,6 +3903,11 @@ class INx2GraphicsSystemSession final : public ServiceFrameworkDecoderMutex(id); + std::unique_lock image_lock; + if (image_mutex) { + image_lock = std::unique_lock{*image_mutex}; + } std::optional> unmap; { std::scoped_lock lk{m_objects->mutex}; diff --git a/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h b/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h index 188193bad4..569a9b4c39 100644 --- a/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h +++ b/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h @@ -2419,8 +2419,8 @@ inline LoweredProgram LowerToMaxwellWith(std::span code, u32 insn.Bits(81, 3) != 7 || insn.Bits(59, 1) || (bindless && (offset_mode || depth)) || (offset_mode && source_b == 255) || - (source_a != 255 && - source_a + in_a > ((array || meta) ? shadow_boundary : 246U)) || (source_b != 255 && source_b + in_b > 246) || + (source_a != 255 && source_a + in_a > shadow_boundary) || + (source_b != 255 && source_b + in_b > shadow_boundary) || insn.Bits(89, 1) || mask == 0 || (bindless ? (handle >= 246 || insn.Bits(46, 13) || !insn.Bits(91, 1)) : (insn.Bits(40, 14) > 0x1fff || binding >= 18 || @@ -2477,20 +2477,31 @@ inline LoweredProgram LowerToMaxwellWith(std::span code, u32 const u32 dimension = insn.Bits(61, 3), lod = insn.Bits(87, 3); const u32 source_a = insn.Bits(24, 8), source_b = insn.Bits(32, 8); if (!bindless && dimension == 5 && depth_compare && lod == 0 && !split && mask == 1) { + const u32 binding = insn.Bits(54, 5); if (!insn.Bits(60, 1) || insn.Bits(76, 2) || insn.Bits(81, 3) != 7 || - insn.Bits(59, 1) || insn.Bits(40, 14) > 0x1fff || insn.Bits(54, 5) != 2 || + insn.Bits(59, 1) || insn.Bits(40, 14) > 0x1fff || binding >= 18 || (source_a & 1) || (source_b & 1) || source_a >= 248 || source_b >= 248 || dest >= 248) return fail(index, "native array depth comparison sample mode"); - if (!bind_texture_buffer(2)) return fail(index, "native texture descriptor buffer conflict"); - for (u32 i = 0; i < 3; ++i) { - const u32 source = i < 2 ? source_a + i : source_b; - emit(0x5c98000000000000ULL | predicate | (250 + i) | - (u64{15} << 39) | (u64{source} << 20)); + // A second descriptor buffer is read bindless: TEX.B takes the handle, loaded from + // that buffer, as its first meta register, then the depth reference. + const bool second_buffer = !bind_texture_buffer(binding); + const auto copy = [&](u32 target, u32 source) { + emit(0x5c98000000000000ULL | predicate | target | (u64{15} << 39) | (u64{source} << 20)); + }; + for (u32 i = 0; i < 3; ++i) copy(250 + i, i < 2 ? source_a + i : source_b); + if (second_buffer) { + emit(0x4c98000000070000ULL | 246 | (u64{15} << 39) | (u64{insn.Bits(40, 14)} << 20) | + (u64{binding} << 34)); + copy(247, source_b + 1); + emit(0xde80000000000000ULL | predicate | dest | (u64{250} << 8) | (u64{246} << 20) | + (u64{3} << 28) | (u64{1} << 31) | (u64{insn.Bits(90, 1)} << 49) | + (u64{1} << 50) | (u64{7} << 51)); + } else { + emit(0xc000000000000000ULL | predicate | dest | (u64{250} << 8) | + (u64{source_b + 1} << 20) | (u64{3} << 28) | (u64{1} << 31) | + (u64{insn.Bits(40, 14)} << 36) | (u64{insn.Bits(90, 1)} << 49) | + (u64{1} << 50) | (u64{7} << 51)); } - emit(0xc000000000000000ULL | predicate | dest | (u64{250} << 8) | - (u64{source_b + 1} << 20) | (u64{3} << 28) | (u64{1} << 31) | - (u64{insn.Bits(40, 14)} << 36) | (u64{insn.Bits(90, 1)} << 49) | - (u64{1} << 50) | (u64{7} << 51)); break; } // General form: gather the native operand list (array index first, coordinates, diff --git a/src/tests/video_core/ampere_lowering.cpp b/src/tests/video_core/ampere_lowering.cpp index f0ed98598e..809a233302 100644 --- a/src/tests/video_core/ampere_lowering.cpp +++ b/src/tests/video_core/ampere_lowering.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "shader_recompiler/frontend/ampere/lower_to_maxwell.h" @@ -440,4 +441,100 @@ TEST_CASE("Ampere Ocarina byte constant loads, ULEA carries to UP3 and two-sourc REQUIRE(static_cast(convert)); CHECK(field(FindMaxwell(convert, 0x5cb, 52), 41, 2) == 3); } +TEST_CASE("Ampere TLD4 lowers per-pixel offsets", "[video_core][ampere]") { + // Welcome Tour's TLD4.PTP instruction: x/y in R4/R5 and packed per-tap offsets in R8/R9. + const std::array code{{ + {{0x040a7b63, 0x33800408, 0x041e2f08, 0x100fc200}}, + EXIT, + }}; + const auto lowered = LowerToMaxwell(code); + INFO(lowered.error); + REQUIRE(static_cast(lowered)); + + const u64 gather = FindMaxwell(lowered, 0xc8, 56); + REQUIRE(gather != 0); + CHECK(((gather >> 28) & 7) == 2); // 2D + CHECK(((gather >> 20) & 0xff) == 246); // packed PTP offsets in the meta register + CHECK(((gather >> 54) & 3) == 2); // per-pixel offsets +} + +TEST_CASE("Ampere TLD4 rejects shadowed per-tap offset registers", "[video_core][ampere]") { + std::array code{{ + {{0x040a7b63, 0x338004f2, 0x041e2f08, 0x100fc200}}, + EXIT, + }}; + REQUIRE(static_cast(LowerToMaxwell(code))); + code[0].words[1] = 0x338004f3; + CHECK_FALSE(static_cast(LowerToMaxwell(code))); + code[0].words[1] = 0x338004f4; + CHECK_FALSE(static_cast(LowerToMaxwell(code))); +} + +TEST_CASE("Ampere TLD4 lowers a single offset register", "[video_core][ampere]") { + std::array code{{ + {{0x040a7b63, 0x33800408, 0x041e1f08, 0x100fc200}}, + EXIT, + }}; + const auto lowered = LowerToMaxwell(code); + REQUIRE(static_cast(lowered)); + const u64 gather = FindMaxwell(lowered, 0xc8, 56); + REQUIRE(gather != 0); + CHECK(((gather >> 54) & 3) == 1); + code[0].words[2] |= 1U << 13; + CHECK_FALSE(static_cast(LowerToMaxwell(code))); +} + +TEST_CASE("Ampere TLD4 packs half results in component order", "[video_core][ampere]") { + const std::array code{{ + {{0x040a7b63, 0x33800408, 0x041eaf08, 0x100fc200}}, + EXIT, + }}; + const auto lowered = LowerToMaxwell(code); + REQUIRE(static_cast(lowered)); + std::vector conversions; + for (const u64 word : lowered.instructions) { + if ((word >> 48) == 0x5ca8) { + conversions.push_back(word); + } + } + REQUIRE(conversions.size() == 4); + CHECK(((conversions[0] >> 20) & 0xff) == 249); + CHECK(((conversions[1] >> 20) & 0xff) == 248); + CHECK(((conversions[2] >> 20) & 0xff) == 247); + CHECK(((conversions[3] >> 20) & 0xff) == 246); +} + +TEST_CASE("Ampere array depth samples use a second descriptor buffer", "[video_core][ampere]") { + const std::array code{{ + {{0x040a7b60, 0xb0000008, 0x000e41ff, 0}}, + {{0x040a7b60, 0xb0400008, 0x000e41ff, 0}}, + EXIT, + }}; + const auto lowered = LowerToMaxwell(code); + REQUIRE(static_cast(lowered)); + CHECK(lowered.required_texture_buffer == 0); + constexpr u64 sample_mask = 0xffc0000000000000ULL; + const auto count_sample = [&](u64 opcode) { + return std::count_if(lowered.instructions.begin(), lowered.instructions.end(), + [=](u64 word) { return (word & sample_mask) == opcode; }); + }; + CHECK(count_sample(0xc000000000000000ULL) == 1); + CHECK(count_sample(0xde00000000000000ULL) == 0); + CHECK(count_sample(0xde80000000000000ULL) == 1); + CHECK(count_sample(0xdec0000000000000ULL) == 0); + const u64 bound = FindMaxwell(lowered, 0xc0, 56); + const u64 bindless = FindMaxwell(lowered, 0xde, 56); + REQUIRE(bound != 0); + REQUIRE(bindless != 0); + CHECK(((bound >> 36) & 0x1fff) == 0); + CHECK(((bound >> 20) & 0xff) == 9); + CHECK(((bindless >> 20) & 0xff) == 246); + const u64 handle_load = FindMaxwell(lowered, 0x4c98, 48); + REQUIRE(handle_load != 0); + CHECK((handle_load & 0xff) == 246); + CHECK(((handle_load >> 34) & 0x1f) == 1); + CHECK(((handle_load >> 20) & 0x3fff) == 0); + CHECK(std::find(lowered.instructions.begin(), lowered.instructions.end(), + 0x5c980780009700f7ULL) != lowered.instructions.end()); +} } // namespace diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 783c77ddae..216725d034 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -597,7 +597,24 @@ void TraceWindowTarget(const Tegra::Engines::Maxwell3D& maxwell3d, Tegra::Memory }(); const u64 pixel_shader = (u64{regs.pipelines[5].reserved[0] & 0xff} << 32) | regs.pipelines[5].reserved[1]; - if (!trace || regs.rt[0].width != 3840 || traced >= 400 || seen_events++ < skip || + if (!trace) { + return; + } + u32 width = regs.rt[0].width; + if (regs.rt[0].tile_mode.is_pitch_linear) { + const auto format = VideoCore::Surface::TryPixelFormatFromRenderTargetFormat(regs.rt[0].format); + if (!format) { + static u32 unknown_formats{}; + if (unknown_formats++ < 8) { + LOG_INFO(HW_GPU, "nx2 window {} undecodable pitch-linear rt0={:#x} fmt={:#x} pitch={} height={}", + kind, regs.rt[0].Address(), static_cast(regs.rt[0].format), + regs.rt[0].width, regs.rt[0].height); + } + return; + } + width /= VideoCore::Surface::BytesPerBlock(*format); + } + if (width < 1280 || traced >= 600 || seen_events++ < skip || (only_ps != 0 && pixel_shader != only_ps)) { return; } @@ -605,10 +622,12 @@ void TraceWindowTarget(const Tegra::Engines::Maxwell3D& maxwell3d, Tegra::Memory const auto& vp = regs.viewport_transform[0]; const auto& sc = regs.scissor_test[0]; LOG_INFO(HW_GPU, - "nx2 window {} rt0={:#x} count={} clear={:#x} color=({},{},{},{}) mask_common={} " + "nx2 window {} rt0={:#x} fmt={:#x} {}x{} count={} clear={:#x} color=({},{},{},{}) mask_common={} " "mask0={:#x} rt_count={} vp scale=({},{}) translate=({},{}) scissor en={} x={}..{} " "y={}..{} blend0={} vs={:#x} ps={:#x} rasterize={}", - kind, regs.rt[0].Address(), count, regs.clear_surface.raw, regs.clear_color[0], + kind, regs.rt[0].Address(), static_cast(regs.rt[0].format), width, + regs.rt[0].height, count, + regs.clear_surface.raw, regs.clear_color[0], regs.clear_color[1], regs.clear_color[2], regs.clear_color[3], regs.color_mask_common, regs.color_mask[0].raw, regs.rt_control.count.Value(), vp.scale_x, vp.scale_y, vp.translate_x, vp.translate_y, sc.enable, sc.min_x.Value(), sc.max_x.Value(), @@ -619,12 +638,23 @@ void TraceWindowTarget(const Tegra::Engines::Maxwell3D& maxwell3d, Tegra::Memory if (kind[0] != 'd') { return; } + const auto read_trace = [&](GPUVAddr address, void* data, size_t size) { + if (size == 0 || address > std::numeric_limits::max() - (size - 1) || + !memory.IsWithinGPUAddressRange(address) || + !memory.IsWithinGPUAddressRange(address + size - 1) || + !memory.IsFullyMappedRange(address, size)) { + return false; + } + memory.ReadBlockUnsafe(address, data, size); + return true; + }; // Pixel-stage constant buffer 3: colour constants of the loading-screen passes. if (const auto& cb3 = maxwell3d.state.shader_stages[4].const_buffers[3]; cb3.enabled) { std::array values{}; - memory.ReadBlockUnsafe(cb3.address, values.data(), sizeof(values)); - LOG_INFO(HW_GPU, "nx2 window ps c[3]@{:#x} size={}: {}", cb3.address, cb3.size, - fmt::join(values, " ")); + if (cb3.size >= sizeof(values) && read_trace(cb3.address, values.data(), sizeof(values))) { + LOG_INFO(HW_GPU, "nx2 window ps c[3]@{:#x} size={}: {}", cb3.address, cb3.size, + fmt::join(values, " ")); + } } static u32 texture_traces{}; if (texture_traces++ > 200) { @@ -632,11 +662,17 @@ void TraceWindowTarget(const Tegra::Engines::Maxwell3D& maxwell3d, Tegra::Memory } // Texture used by the pixel shader's first bound TEX (handle in c[2][0x20]) and its TIC. const auto& cb = maxwell3d.state.shader_stages[4].const_buffers[2]; - const u32 handle = cb.enabled ? memory.Read(cb.address + 0x20) : 0; + u32 handle{}; + if (!cb.enabled || cb.size < 0x24 || cb.address > std::numeric_limits::max() - 0x20 || + !read_trace(cb.address + 0x20, &handle, sizeof(handle))) { + return; + } const u32 tic_index = handle & 0xfffff; const GPUVAddr tic_address = regs.tex_header.Address() + u64{tic_index} * 32; std::array tic{}; - memory.ReadBlockUnsafe(tic_address, tic.data(), sizeof(tic)); + if (tic_index > regs.tex_header.limit || !read_trace(tic_address, tic.data(), sizeof(tic))) { + return; + } LOG_INFO(HW_GPU, "nx2 window texture: cb2={:#x} en={} handle={:#x} tic pool={:#x} limit={} " "sampler pool={:#x} tic[{}]={:08x}", @@ -645,16 +681,19 @@ void TraceWindowTarget(const Tegra::Engines::Maxwell3D& maxwell3d, Tegra::Memory // First bytes of the texture's memory (block-linear, so just a sample). const GPUVAddr texture = (GPUVAddr{tic[2] & 0xffff} << 32) | tic[1]; std::array texels{}; - memory.ReadBlockUnsafe(texture, texels.data(), sizeof(texels)); + if (!read_trace(texture, texels.data(), sizeof(texels))) { + return; + } LOG_INFO(HW_GPU, "nx2 window texture {:#x} {}x{}: {:08x}", texture, (tic[4] & 0xffff) + 1, (tic[5] & 0xffff) + 1, fmt::join(texels, " ")); // EDEN_NX2_TEXTURE_DUMP=: save the first 4 MiB of the last traced texture's memory. if (const char* path = std::getenv("EDEN_NX2_TEXTURE_DUMP"); path != nullptr) { - std::vector bytes(2048u * 10240u); - memory.ReadBlockUnsafe(texture, bytes.data(), bytes.size()); - if (std::FILE* file = std::fopen(path, "wb")) { - std::fwrite(bytes.data(), 1, bytes.size(), file); - std::fclose(file); + std::vector bytes(4 * 1024 * 1024); + if (read_trace(texture, bytes.data(), bytes.size())) { + if (std::FILE* file = std::fopen(path, "wb")) { + std::fwrite(bytes.data(), 1, bytes.size(), file); + std::fclose(file); + } } } } @@ -854,6 +893,7 @@ void RasterizerVulkan::DrawIndirect() { } } buffer_cache.SetDrawIndirect(¶ms); + TraceWindowTarget(*maxwell3d, *gpu_memory, "draw-indirect", params.max_draw_counts); PrepareDraw(params.is_indexed, [this, ¶ms] { if (mesh_draw) { // Indirect task/mesh draws would need vkCmdDrawMeshTasksIndirectEXT's layout. diff --git a/src/video_core/shader_environment.cpp b/src/video_core/shader_environment.cpp index 6d6ebfd40f..2175c3e098 100644 --- a/src/video_core/shader_environment.cpp +++ b/src/video_core/shader_environment.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -377,6 +378,25 @@ Tegra::Texture::TICEntry GenericEnvironment::ReadTextureInfo(GPUVAddr tic_addr, const GPUVAddr descriptor_addr{tic_addr + index * sizeof(Tegra::Texture::TICEntry)}; Tegra::Texture::TICEntry entry; gpu_memory->ReadBlock(descriptor_addr, &entry, sizeof(entry)); + // Diagnostic: the first descriptors the programs read, with the fields that decide the + // sampled colour (format, component types, channel swizzle) and the raw words. + static const bool diagnostics = std::getenv("EDEN_NATIVE_DRAW_DIAGNOSTICS") != nullptr; + static std::atomic described{}; + if (diagnostics && described.fetch_add(1, std::memory_order_relaxed) < 150) { + std::array words{}; + gpu_memory->ReadBlock(descriptor_addr, words.data(), sizeof(words)); + LOG_INFO(Shader, + "TIC {:#x} idx {} fmt {:#x} types {}/{}/{}/{} swz {}/{}/{}/{} type {} {}x{}x{} " + "srgb {}: {:08x} {:08x} {:08x} {:08x}", + descriptor_addr, index, static_cast(entry.format.Value()), + static_cast(entry.r_type.Value()), static_cast(entry.g_type.Value()), + static_cast(entry.b_type.Value()), static_cast(entry.a_type.Value()), + static_cast(entry.x_source.Value()), static_cast(entry.y_source.Value()), + static_cast(entry.z_source.Value()), static_cast(entry.w_source.Value()), + static_cast(entry.texture_type.Value()), entry.Width(), entry.Height(), + entry.Depth(), static_cast(entry.srgb_conversion.Value()), words[0], words[1], + words[2], words[3]); + } return entry; }