From 88ba5a7f22b5783e6e19059e49b632d0bd9c8da0 Mon Sep 17 00:00:00 2001 From: ameerj <52414509+ameerj@users.noreply.github.com> Date: Sun, 18 Dec 2022 17:44:34 -0500 Subject: common: add make_unique_for_overwrite --- src/common/CMakeLists.txt | 1 + src/common/make_unique_for_overwrite.h | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 src/common/make_unique_for_overwrite.h (limited to 'src/common') diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 25b22a281..f558f5a58 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -78,6 +78,7 @@ add_library(common STATIC logging/types.h lz4_compression.cpp lz4_compression.h + make_unique_for_overwrite.h math_util.h memory_detect.cpp memory_detect.h diff --git a/src/common/make_unique_for_overwrite.h b/src/common/make_unique_for_overwrite.h new file mode 100644 index 000000000..c7413cf51 --- /dev/null +++ b/src/common/make_unique_for_overwrite.h @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +namespace Common { + +template +requires(!std::is_array_v) std::unique_ptr make_unique_for_overwrite() { + return std::unique_ptr(new T); +} + +template +requires std::is_unbounded_array_v std::unique_ptr make_unique_for_overwrite(std::size_t n) { + return std::unique_ptr(new std::remove_extent_t[n]); +} + +template +requires std::is_bounded_array_v +void make_unique_for_overwrite(Args&&...) = delete; + +} // namespace Common -- cgit v1.2.3 From cfc34dd41d63f45b0587d089b8ec7fc2ed27c04e Mon Sep 17 00:00:00 2001 From: ameerj <52414509+ameerj@users.noreply.github.com> Date: Sun, 18 Dec 2022 18:08:20 -0500 Subject: common: Add ScratchBuffer class This class creates a default initialized heap allocated buffer for cases where value initializing members during allocation or resize is redundant. --- src/common/CMakeLists.txt | 1 + src/common/scratch_buffer.h | 74 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 src/common/scratch_buffer.h (limited to 'src/common') diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index f558f5a58..eb05e46a8 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -102,6 +102,7 @@ add_library(common STATIC ${CMAKE_CURRENT_BINARY_DIR}/scm_rev.cpp scm_rev.h scope_exit.h + scratch_buffer.h settings.cpp settings.h settings_input.cpp diff --git a/src/common/scratch_buffer.h b/src/common/scratch_buffer.h new file mode 100644 index 000000000..afbe2eee1 --- /dev/null +++ b/src/common/scratch_buffer.h @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/make_unique_for_overwrite.h" + +namespace Common { + +/** + * ScratchBuffer class + * This class creates a default initialized heap allocated buffer for cases such as intermediate + * buffers being copied into entirely, where value initializing members during allocation or resize + * is redundant. + */ +template +class ScratchBuffer { +public: + ScratchBuffer() = default; + + explicit ScratchBuffer(size_t initial_capacity) + : last_requested_size{initial_capacity}, capacity{initial_capacity}, + buffer{Common::make_unique_for_overwrite(initial_capacity)} {} + + ~ScratchBuffer() = default; + + /// This will only grow the buffer's capacity if size is greater than the current capacity. + void resize(size_t size) { + if (size > capacity) { + capacity = size; + buffer = Common::make_unique_for_overwrite(capacity); + } + last_requested_size = size; + } + + [[nodiscard]] T* data() noexcept { + return buffer.get(); + } + + [[nodiscard]] const T* data() const noexcept { + return buffer.get(); + } + + [[nodiscard]] T* begin() noexcept { + return data(); + } + + [[nodiscard]] const T* begin() const noexcept { + return data(); + } + + [[nodiscard]] T* end() noexcept { + return data() + last_requested_size; + } + + [[nodiscard]] const T* end() const noexcept { + return data() + last_requested_size; + } + + [[nodiscard]] T& operator[](size_t i) { + return buffer[i]; + } + + [[nodiscard]] size_t size() const noexcept { + return last_requested_size; + } + +private: + size_t last_requested_size{}; + size_t capacity{}; + std::unique_ptr buffer{}; +}; + +} // namespace Common -- cgit v1.2.3 From 64869807e2e4604f3d6334feeaf890515e9edb81 Mon Sep 17 00:00:00 2001 From: ameerj <52414509+ameerj@users.noreply.github.com> Date: Mon, 19 Dec 2022 12:30:52 -0500 Subject: tests: Add ScratchBuffer tests --- src/common/scratch_buffer.h | 14 ++-- src/tests/CMakeLists.txt | 1 + src/tests/common/scratch_buffer.cpp | 127 ++++++++++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+), 5 deletions(-) create mode 100644 src/tests/common/scratch_buffer.cpp (limited to 'src/common') diff --git a/src/common/scratch_buffer.h b/src/common/scratch_buffer.h index afbe2eee1..59bb8a9ea 100644 --- a/src/common/scratch_buffer.h +++ b/src/common/scratch_buffer.h @@ -19,16 +19,16 @@ public: ScratchBuffer() = default; explicit ScratchBuffer(size_t initial_capacity) - : last_requested_size{initial_capacity}, capacity{initial_capacity}, + : last_requested_size{initial_capacity}, buffer_capacity{initial_capacity}, buffer{Common::make_unique_for_overwrite(initial_capacity)} {} ~ScratchBuffer() = default; /// This will only grow the buffer's capacity if size is greater than the current capacity. void resize(size_t size) { - if (size > capacity) { - capacity = size; - buffer = Common::make_unique_for_overwrite(capacity); + if (size > buffer_capacity) { + buffer_capacity = size; + buffer = Common::make_unique_for_overwrite(buffer_capacity); } last_requested_size = size; } @@ -65,9 +65,13 @@ public: return last_requested_size; } + [[nodiscard]] size_t capacity() const noexcept { + return buffer_capacity; + } + private: size_t last_requested_size{}; - size_t capacity{}; + size_t buffer_capacity{}; std::unique_ptr buffer{}; }; diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 348d1edf4..6a4022e45 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -8,6 +8,7 @@ add_executable(tests common/host_memory.cpp common/param_package.cpp common/ring_buffer.cpp + common/scratch_buffer.cpp common/unique_function.cpp core/core_timing.cpp core/internal_network/network.cpp diff --git a/src/tests/common/scratch_buffer.cpp b/src/tests/common/scratch_buffer.cpp new file mode 100644 index 000000000..a59490f55 --- /dev/null +++ b/src/tests/common/scratch_buffer.cpp @@ -0,0 +1,127 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include "common/common_types.h" +#include "common/scratch_buffer.h" + +namespace Common { + +TEST_CASE("ScratchBuffer: Basic Test", "[common]") { + ScratchBuffer buf; + + REQUIRE(buf.size() == 0U); + REQUIRE(buf.capacity() == 0U); + + std::array payload; + payload.fill(66); + + buf.resize(payload.size()); + REQUIRE(buf.size() == payload.size()); + REQUIRE(buf.capacity() == payload.size()); + + std::memcpy(buf.data(), payload.data(), payload.size()); + for (size_t i = 0; i < payload.size(); ++i) { + REQUIRE(buf[i] == payload[i]); + } +} + +TEST_CASE("ScratchBuffer: Resize Grow", "[common]") { + std::array payload; + payload.fill(66); + + ScratchBuffer buf(payload.size()); + REQUIRE(buf.size() == payload.size()); + REQUIRE(buf.capacity() == payload.size()); + + // Increasing the size should reallocate the buffer + buf.resize(payload.size() * 2); + REQUIRE(buf.size() == payload.size() * 2); + REQUIRE(buf.capacity() == payload.size() * 2); + + // Since the buffer is not value initialized, reading its data will be garbage +} + +TEST_CASE("ScratchBuffer: Resize Shrink", "[common]") { + std::array payload; + payload.fill(66); + + ScratchBuffer buf(payload.size()); + REQUIRE(buf.size() == payload.size()); + REQUIRE(buf.capacity() == payload.size()); + + std::memcpy(buf.data(), payload.data(), payload.size()); + for (size_t i = 0; i < payload.size(); ++i) { + REQUIRE(buf[i] == payload[i]); + } + + // Decreasing the size should not cause a buffer reallocation + // This can be tested by ensuring the buffer capacity and data has not changed, + buf.resize(1U); + REQUIRE(buf.size() == 1U); + REQUIRE(buf.capacity() == payload.size()); + + for (size_t i = 0; i < payload.size(); ++i) { + REQUIRE(buf[i] == payload[i]); + } +} + +TEST_CASE("ScratchBuffer: Span Size", "[common]") { + std::array payload; + payload.fill(66); + + ScratchBuffer buf(payload.size()); + REQUIRE(buf.size() == payload.size()); + REQUIRE(buf.capacity() == payload.size()); + + std::memcpy(buf.data(), payload.data(), payload.size()); + for (size_t i = 0; i < payload.size(); ++i) { + REQUIRE(buf[i] == payload[i]); + } + + buf.resize(3U); + REQUIRE(buf.size() == 3U); + REQUIRE(buf.capacity() == payload.size()); + + const auto buf_span = std::span(buf); + // The span size is the last requested size of the buffer, not its capacity + REQUIRE(buf_span.size() == buf.size()); + + for (size_t i = 0; i < buf_span.size(); ++i) { + REQUIRE(buf_span[i] == buf[i]); + REQUIRE(buf_span[i] == payload[i]); + } +} + +TEST_CASE("ScratchBuffer: Span Writes", "[common]") { + std::array payload; + payload.fill(66); + + ScratchBuffer buf(payload.size()); + REQUIRE(buf.size() == payload.size()); + REQUIRE(buf.capacity() == payload.size()); + + std::memcpy(buf.data(), payload.data(), payload.size()); + for (size_t i = 0; i < payload.size(); ++i) { + REQUIRE(buf[i] == payload[i]); + } + + buf.resize(3U); + REQUIRE(buf.size() == 3U); + REQUIRE(buf.capacity() == payload.size()); + + const auto buf_span = std::span(buf); + REQUIRE(buf_span.size() == buf.size()); + + for (size_t i = 0; i < buf_span.size(); ++i) { + const auto new_value = static_cast(i + 1U); + // Writes to a span of the scratch buffer will propogate to the buffer itself + buf_span[i] = new_value; + REQUIRE(buf[i] == new_value); + } +} + +} // namespace Common -- cgit v1.2.3 From c6590ad07b384762fd90ee8852796ec681a69286 Mon Sep 17 00:00:00 2001 From: ameerj <52414509+ameerj@users.noreply.github.com> Date: Mon, 19 Dec 2022 22:40:50 -0500 Subject: scratch_buffer: Explicitly defing resize and resize_destructive functions resize keeps previous data intact when the buffer grows resize_destructive destroys the previous data when the buffer grows --- src/common/scratch_buffer.h | 17 +++++++ src/tests/common/scratch_buffer.cpp | 78 ++++++++++++++++++++++++++++-- src/video_core/buffer_cache/buffer_cache.h | 2 +- src/video_core/dma_pusher.cpp | 2 +- src/video_core/engines/engine_upload.cpp | 4 +- src/video_core/engines/maxwell_dma.cpp | 18 +++---- src/video_core/host1x/vic.cpp | 6 +-- 7 files changed, 108 insertions(+), 19 deletions(-) (limited to 'src/common') diff --git a/src/common/scratch_buffer.h b/src/common/scratch_buffer.h index 59bb8a9ea..1245a5086 100644 --- a/src/common/scratch_buffer.h +++ b/src/common/scratch_buffer.h @@ -25,7 +25,20 @@ public: ~ScratchBuffer() = default; /// This will only grow the buffer's capacity if size is greater than the current capacity. + /// The previously held data will remain intact. void resize(size_t size) { + if (size > buffer_capacity) { + auto new_buffer = Common::make_unique_for_overwrite(size); + std::move(buffer.get(), buffer.get() + buffer_capacity, new_buffer.get()); + buffer = std::move(new_buffer); + buffer_capacity = size; + } + last_requested_size = size; + } + + /// This will only grow the buffer's capacity if size is greater than the current capacity. + /// The previously held data will be destroyed if a reallocation occurs. + void resize_destructive(size_t size) { if (size > buffer_capacity) { buffer_capacity = size; buffer = Common::make_unique_for_overwrite(buffer_capacity); @@ -61,6 +74,10 @@ public: return buffer[i]; } + [[nodiscard]] const T& operator[](size_t i) const { + return buffer[i]; + } + [[nodiscard]] size_t size() const noexcept { return last_requested_size; } diff --git a/src/tests/common/scratch_buffer.cpp b/src/tests/common/scratch_buffer.cpp index a59490f55..b602c8d0a 100644 --- a/src/tests/common/scratch_buffer.cpp +++ b/src/tests/common/scratch_buffer.cpp @@ -29,7 +29,7 @@ TEST_CASE("ScratchBuffer: Basic Test", "[common]") { } } -TEST_CASE("ScratchBuffer: Resize Grow", "[common]") { +TEST_CASE("ScratchBuffer: resize_destructive Grow", "[common]") { std::array payload; payload.fill(66); @@ -38,14 +38,86 @@ TEST_CASE("ScratchBuffer: Resize Grow", "[common]") { REQUIRE(buf.capacity() == payload.size()); // Increasing the size should reallocate the buffer - buf.resize(payload.size() * 2); + buf.resize_destructive(payload.size() * 2); REQUIRE(buf.size() == payload.size() * 2); REQUIRE(buf.capacity() == payload.size() * 2); // Since the buffer is not value initialized, reading its data will be garbage } -TEST_CASE("ScratchBuffer: Resize Shrink", "[common]") { +TEST_CASE("ScratchBuffer: resize_destructive Shrink", "[common]") { + std::array payload; + payload.fill(66); + + ScratchBuffer buf(payload.size()); + REQUIRE(buf.size() == payload.size()); + REQUIRE(buf.capacity() == payload.size()); + + std::memcpy(buf.data(), payload.data(), payload.size()); + for (size_t i = 0; i < payload.size(); ++i) { + REQUIRE(buf[i] == payload[i]); + } + + // Decreasing the size should not cause a buffer reallocation + // This can be tested by ensuring the buffer capacity and data has not changed, + buf.resize_destructive(1U); + REQUIRE(buf.size() == 1U); + REQUIRE(buf.capacity() == payload.size()); + + for (size_t i = 0; i < payload.size(); ++i) { + REQUIRE(buf[i] == payload[i]); + } +} + +TEST_CASE("ScratchBuffer: resize Grow u8", "[common]") { + std::array payload; + payload.fill(66); + + ScratchBuffer buf(payload.size()); + REQUIRE(buf.size() == payload.size()); + REQUIRE(buf.capacity() == payload.size()); + + std::memcpy(buf.data(), payload.data(), payload.size()); + for (size_t i = 0; i < payload.size(); ++i) { + REQUIRE(buf[i] == payload[i]); + } + + // Increasing the size should reallocate the buffer + buf.resize(payload.size() * 2); + REQUIRE(buf.size() == payload.size() * 2); + REQUIRE(buf.capacity() == payload.size() * 2); + + // resize() keeps the previous data intact + for (size_t i = 0; i < payload.size(); ++i) { + REQUIRE(buf[i] == payload[i]); + } +} + +TEST_CASE("ScratchBuffer: resize Grow u64", "[common]") { + std::array payload; + payload.fill(6666); + + ScratchBuffer buf(payload.size()); + REQUIRE(buf.size() == payload.size()); + REQUIRE(buf.capacity() == payload.size()); + + std::memcpy(buf.data(), payload.data(), payload.size() * sizeof(u64)); + for (size_t i = 0; i < payload.size(); ++i) { + REQUIRE(buf[i] == payload[i]); + } + + // Increasing the size should reallocate the buffer + buf.resize(payload.size() * 2); + REQUIRE(buf.size() == payload.size() * 2); + REQUIRE(buf.capacity() == payload.size() * 2); + + // resize() keeps the previous data intact + for (size_t i = 0; i < payload.size(); ++i) { + REQUIRE(buf[i] == payload[i]); + } +} + +TEST_CASE("ScratchBuffer: resize Shrink", "[common]") { std::array payload; payload.fill(66); diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index a8bd5585b..6c8d98946 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -1926,7 +1926,7 @@ std::span BufferCache

::ImmediateBufferWithData(VAddr cpu_addr, size template std::span BufferCache

::ImmediateBuffer(size_t wanted_capacity) { - immediate_buffer_alloc.resize(wanted_capacity); + immediate_buffer_alloc.resize_destructive(wanted_capacity); return std::span(immediate_buffer_alloc.data(), wanted_capacity); } diff --git a/src/video_core/dma_pusher.cpp b/src/video_core/dma_pusher.cpp index d1f541bf5..322de2606 100644 --- a/src/video_core/dma_pusher.cpp +++ b/src/video_core/dma_pusher.cpp @@ -74,7 +74,7 @@ bool DmaPusher::Step() { } // Push buffer non-empty, read a word - command_headers.resize(command_list_header.size); + command_headers.resize_destructive(command_list_header.size); if (Settings::IsGPULevelHigh()) { memory_manager.ReadBlock(dma_get, command_headers.data(), command_list_header.size * sizeof(u32)); diff --git a/src/video_core/engines/engine_upload.cpp b/src/video_core/engines/engine_upload.cpp index e4f8331ab..cea1dd8b0 100644 --- a/src/video_core/engines/engine_upload.cpp +++ b/src/video_core/engines/engine_upload.cpp @@ -24,7 +24,7 @@ void State::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) { void State::ProcessExec(const bool is_linear_) { write_offset = 0; copy_size = regs.line_length_in * regs.line_count; - inner_buffer.resize(copy_size); + inner_buffer.resize_destructive(copy_size); is_linear = is_linear_; } @@ -70,7 +70,7 @@ void State::ProcessData(std::span read_buffer) { const std::size_t dst_size = Tegra::Texture::CalculateSize( true, bytes_per_pixel, width, regs.dest.height, regs.dest.depth, regs.dest.BlockHeight(), regs.dest.BlockDepth()); - tmp_buffer.resize(dst_size); + tmp_buffer.resize_destructive(dst_size); memory_manager.ReadBlock(address, tmp_buffer.data(), dst_size); Tegra::Texture::SwizzleSubrect(tmp_buffer, read_buffer, bytes_per_pixel, width, regs.dest.height, regs.dest.depth, x_offset, regs.dest.y, diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp index dc873732e..f73d7bf0f 100644 --- a/src/video_core/engines/maxwell_dma.cpp +++ b/src/video_core/engines/maxwell_dma.cpp @@ -184,8 +184,8 @@ void MaxwellDMA::CopyBlockLinearToPitch() { const size_t src_size = CalculateSize(true, bytes_per_pixel, width, height, depth, block_height, block_depth); - read_buffer.resize(src_size); - write_buffer.resize(dst_size); + read_buffer.resize_destructive(src_size); + write_buffer.resize_destructive(dst_size); memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size); memory_manager.ReadBlock(regs.offset_out, write_buffer.data(), dst_size); @@ -231,8 +231,8 @@ void MaxwellDMA::CopyPitchToBlockLinear() { CalculateSize(true, bytes_per_pixel, width, height, depth, block_height, block_depth); const size_t src_size = static_cast(regs.pitch_in) * regs.line_count; - read_buffer.resize(src_size); - write_buffer.resize(dst_size); + read_buffer.resize_destructive(src_size); + write_buffer.resize_destructive(dst_size); memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size); if (Settings::IsGPULevelExtreme()) { @@ -261,8 +261,8 @@ void MaxwellDMA::FastCopyBlockLinearToPitch() { pos_x = pos_x % x_in_gob; pos_y = pos_y % 8; - read_buffer.resize(src_size); - write_buffer.resize(dst_size); + read_buffer.resize_destructive(src_size); + write_buffer.resize_destructive(dst_size); if (Settings::IsGPULevelExtreme()) { memory_manager.ReadBlock(regs.offset_in + offset, read_buffer.data(), src_size); @@ -321,10 +321,10 @@ void MaxwellDMA::CopyBlockLinearToBlockLinear() { const u32 pitch = x_elements * bytes_per_pixel; const size_t mid_buffer_size = pitch * regs.line_count; - read_buffer.resize(src_size); - write_buffer.resize(dst_size); + read_buffer.resize_destructive(src_size); + write_buffer.resize_destructive(dst_size); - intermediate_buffer.resize(mid_buffer_size); + intermediate_buffer.resize_destructive(mid_buffer_size); memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size); memory_manager.ReadBlock(regs.offset_out, write_buffer.data(), dst_size); diff --git a/src/video_core/host1x/vic.cpp b/src/video_core/host1x/vic.cpp index ac0b7d20e..36a04e4e0 100644 --- a/src/video_core/host1x/vic.cpp +++ b/src/video_core/host1x/vic.cpp @@ -155,7 +155,7 @@ void Vic::WriteRGBFrame(const AVFrame* frame, const VicConfig& config) { // swizzle pitch linear to block linear const u32 block_height = static_cast(config.block_linear_height_log2); const auto size = Texture::CalculateSize(true, 4, width, height, 1, block_height, 0); - luma_buffer.resize(size); + luma_buffer.resize_destructive(size); std::span frame_buff(converted_frame_buf_addr, 4 * width * height); Texture::SwizzleSubrect(luma_buffer, frame_buff, 4, width, height, 1, 0, 0, width, height, block_height, 0, width * 4); @@ -181,8 +181,8 @@ void Vic::WriteYUVFrame(const AVFrame* frame, const VicConfig& config) { const auto stride = static_cast(frame->linesize[0]); - luma_buffer.resize(aligned_width * surface_height); - chroma_buffer.resize(aligned_width * surface_height / 2); + luma_buffer.resize_destructive(aligned_width * surface_height); + chroma_buffer.resize_destructive(aligned_width * surface_height / 2); // Populate luma buffer const u8* luma_src = frame->data[0]; -- cgit v1.2.3