Explorar o código

Merge pull request #9453 from ameerj/scratch-vector

common: Add ScratchBuffer Class
Fernando S %!s(int64=3) %!d(string=hai) anos
pai
achega
3e6850f00b

+ 2 - 0
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
@@ -101,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

+ 25 - 0
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 <memory>
+#include <type_traits>
+
+namespace Common {
+
+template <class T>
+requires(!std::is_array_v<T>) std::unique_ptr<T> make_unique_for_overwrite() {
+    return std::unique_ptr<T>(new T);
+}
+
+template <class T>
+requires std::is_unbounded_array_v<T> std::unique_ptr<T> make_unique_for_overwrite(std::size_t n) {
+    return std::unique_ptr<T>(new std::remove_extent_t<T>[n]);
+}
+
+template <class T, class... Args>
+requires std::is_bounded_array_v<T>
+void make_unique_for_overwrite(Args&&...) = delete;
+
+} // namespace Common

+ 95 - 0
src/common/scratch_buffer.h

@@ -0,0 +1,95 @@
+// 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 <typename T>
+class ScratchBuffer {
+public:
+    ScratchBuffer() = default;
+
+    explicit ScratchBuffer(size_t initial_capacity)
+        : last_requested_size{initial_capacity}, buffer_capacity{initial_capacity},
+          buffer{Common::make_unique_for_overwrite<T[]>(initial_capacity)} {}
+
+    ~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<T[]>(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<T[]>(buffer_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]] const T& operator[](size_t i) const {
+        return buffer[i];
+    }
+
+    [[nodiscard]] size_t size() const noexcept {
+        return last_requested_size;
+    }
+
+    [[nodiscard]] size_t capacity() const noexcept {
+        return buffer_capacity;
+    }
+
+private:
+    size_t last_requested_size{};
+    size_t buffer_capacity{};
+    std::unique_ptr<T[]> buffer{};
+};
+
+} // namespace Common

+ 1 - 0
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

+ 199 - 0
src/tests/common/scratch_buffer.cpp

@@ -0,0 +1,199 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <array>
+#include <span>
+#include <catch2/catch.hpp>
+#include "common/common_types.h"
+#include "common/scratch_buffer.h"
+
+namespace Common {
+
+TEST_CASE("ScratchBuffer: Basic Test", "[common]") {
+    ScratchBuffer<u8> buf;
+
+    REQUIRE(buf.size() == 0U);
+    REQUIRE(buf.capacity() == 0U);
+
+    std::array<u8, 10> 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_destructive Grow", "[common]") {
+    std::array<u8, 10> payload;
+    payload.fill(66);
+
+    ScratchBuffer<u8> buf(payload.size());
+    REQUIRE(buf.size() == payload.size());
+    REQUIRE(buf.capacity() == payload.size());
+
+    // Increasing the size should reallocate the buffer
+    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_destructive Shrink", "[common]") {
+    std::array<u8, 10> payload;
+    payload.fill(66);
+
+    ScratchBuffer<u8> 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<u8, 10> payload;
+    payload.fill(66);
+
+    ScratchBuffer<u8> 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<u64, 10> payload;
+    payload.fill(6666);
+
+    ScratchBuffer<u64> 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<u8, 10> payload;
+    payload.fill(66);
+
+    ScratchBuffer<u8> 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<u8, 10> payload;
+    payload.fill(66);
+
+    ScratchBuffer<u8> 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<u8>(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<u8, 10> payload;
+    payload.fill(66);
+
+    ScratchBuffer<u8> 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<u8>(buf);
+    REQUIRE(buf_span.size() == buf.size());
+
+    for (size_t i = 0; i < buf_span.size(); ++i) {
+        const auto new_value = static_cast<u8>(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

+ 4 - 7
src/video_core/buffer_cache/buffer_cache.h

@@ -20,6 +20,7 @@
 #include "common/lru_cache.h"
 #include "common/microprofile.h"
 #include "common/polyfill_ranges.h"
+#include "common/scratch_buffer.h"
 #include "common/settings.h"
 #include "core/memory.h"
 #include "video_core/buffer_cache/buffer_base.h"
@@ -422,8 +423,7 @@ private:
     IntervalSet common_ranges;
     std::deque<IntervalSet> committed_ranges;
 
-    size_t immediate_buffer_capacity = 0;
-    std::unique_ptr<u8[]> immediate_buffer_alloc;
+    Common::ScratchBuffer<u8> immediate_buffer_alloc;
 
     struct LRUItemParams {
         using ObjectType = BufferId;
@@ -1926,11 +1926,8 @@ std::span<const u8> BufferCache<P>::ImmediateBufferWithData(VAddr cpu_addr, size
 
 template <class P>
 std::span<u8> BufferCache<P>::ImmediateBuffer(size_t wanted_capacity) {
-    if (wanted_capacity > immediate_buffer_capacity) {
-        immediate_buffer_capacity = wanted_capacity;
-        immediate_buffer_alloc = std::make_unique<u8[]>(wanted_capacity);
-    }
-    return std::span<u8>(immediate_buffer_alloc.get(), wanted_capacity);
+    immediate_buffer_alloc.resize_destructive(wanted_capacity);
+    return std::span<u8>(immediate_buffer_alloc.data(), wanted_capacity);
 }
 
 template <class P>

+ 11 - 8
src/video_core/dma_pusher.cpp

@@ -56,7 +56,7 @@ bool DmaPusher::Step() {
 
     if (command_list.prefetch_command_list.size()) {
         // Prefetched command list from nvdrv, used for things like synchronization
-        command_headers = std::move(command_list.prefetch_command_list);
+        ProcessCommands(command_list.prefetch_command_list);
         dma_pushbuffer.pop();
     } else {
         const CommandListHeader command_list_header{
@@ -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));
@@ -82,16 +82,21 @@ bool DmaPusher::Step() {
             memory_manager.ReadBlockUnsafe(dma_get, command_headers.data(),
                                            command_list_header.size * sizeof(u32));
         }
+        ProcessCommands(command_headers);
     }
-    for (std::size_t index = 0; index < command_headers.size();) {
-        const CommandHeader& command_header = command_headers[index];
+
+    return true;
+}
+
+void DmaPusher::ProcessCommands(std::span<const CommandHeader> commands) {
+    for (std::size_t index = 0; index < commands.size();) {
+        const CommandHeader& command_header = commands[index];
 
         if (dma_state.method_count) {
             // Data word of methods command
             if (dma_state.non_incrementing) {
                 const u32 max_write = static_cast<u32>(
-                    std::min<std::size_t>(index + dma_state.method_count, command_headers.size()) -
-                    index);
+                    std::min<std::size_t>(index + dma_state.method_count, commands.size()) - index);
                 CallMultiMethod(&command_header.argument, max_write);
                 dma_state.method_count -= max_write;
                 dma_state.is_last_call = true;
@@ -142,8 +147,6 @@ bool DmaPusher::Step() {
         }
         index++;
     }
-
-    return true;
 }
 
 void DmaPusher::SetState(const CommandHeader& command_header) {

+ 6 - 2
src/video_core/dma_pusher.h

@@ -4,11 +4,13 @@
 #pragma once
 
 #include <array>
+#include <span>
 #include <vector>
 #include <queue>
 
 #include "common/bit_field.h"
 #include "common/common_types.h"
+#include "common/scratch_buffer.h"
 #include "video_core/engines/engine_interface.h"
 #include "video_core/engines/puller.h"
 
@@ -136,13 +138,15 @@ private:
     static constexpr u32 non_puller_methods = 0x40;
     static constexpr u32 max_subchannels = 8;
     bool Step();
+    void ProcessCommands(std::span<const CommandHeader> commands);
 
     void SetState(const CommandHeader& command_header);
 
     void CallMethod(u32 argument) const;
     void CallMultiMethod(const u32* base_start, u32 num_methods) const;
 
-    std::vector<CommandHeader> command_headers; ///< Buffer for list of commands fetched at once
+    Common::ScratchBuffer<CommandHeader>
+        command_headers; ///< Buffer for list of commands fetched at once
 
     std::queue<CommandList> dma_pushbuffer; ///< Queue of command lists to be processed
     std::size_t dma_pushbuffer_subindex{};  ///< Index within a command list within the pushbuffer
@@ -159,7 +163,7 @@ private:
     DmaState dma_state{};
     bool dma_increment_once{};
 
-    bool ib_enable{true}; ///< IB mode enabled
+    const bool ib_enable{true}; ///< IB mode enabled
 
     std::array<Engines::EngineInterface*, max_subchannels> subchannels{};
 

+ 2 - 2
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<const u8> 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,

+ 4 - 3
src/video_core/engines/engine_upload.h

@@ -4,9 +4,10 @@
 #pragma once
 
 #include <span>
-#include <vector>
+
 #include "common/bit_field.h"
 #include "common/common_types.h"
+#include "common/scratch_buffer.h"
 
 namespace Tegra {
 class MemoryManager;
@@ -73,8 +74,8 @@ private:
 
     u32 write_offset = 0;
     u32 copy_size = 0;
-    std::vector<u8> inner_buffer;
-    std::vector<u8> tmp_buffer;
+    Common::ScratchBuffer<u8> inner_buffer;
+    Common::ScratchBuffer<u8> tmp_buffer;
     bool is_linear = false;
     Registers& regs;
     MemoryManager& memory_manager;

+ 9 - 25
src/video_core/engines/maxwell_dma.cpp

@@ -184,12 +184,8 @@ void MaxwellDMA::CopyBlockLinearToPitch() {
     const size_t src_size =
         CalculateSize(true, bytes_per_pixel, width, height, depth, block_height, block_depth);
 
-    if (read_buffer.size() < src_size) {
-        read_buffer.resize(src_size);
-    }
-    if (write_buffer.size() < dst_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);
@@ -235,12 +231,8 @@ void MaxwellDMA::CopyPitchToBlockLinear() {
         CalculateSize(true, bytes_per_pixel, width, height, depth, block_height, block_depth);
     const size_t src_size = static_cast<size_t>(regs.pitch_in) * regs.line_count;
 
-    if (read_buffer.size() < src_size) {
-        read_buffer.resize(src_size);
-    }
-    if (write_buffer.size() < dst_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()) {
@@ -269,12 +261,8 @@ void MaxwellDMA::FastCopyBlockLinearToPitch() {
     pos_x = pos_x % x_in_gob;
     pos_y = pos_y % 8;
 
-    if (read_buffer.size() < src_size) {
-        read_buffer.resize(src_size);
-    }
-    if (write_buffer.size() < dst_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);
@@ -333,14 +321,10 @@ void MaxwellDMA::CopyBlockLinearToBlockLinear() {
     const u32 pitch = x_elements * bytes_per_pixel;
     const size_t mid_buffer_size = pitch * regs.line_count;
 
-    if (read_buffer.size() < src_size) {
-        read_buffer.resize(src_size);
-    }
-    if (write_buffer.size() < dst_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);

+ 5 - 3
src/video_core/engines/maxwell_dma.h

@@ -6,8 +6,10 @@
 #include <array>
 #include <cstddef>
 #include <vector>
+
 #include "common/bit_field.h"
 #include "common/common_types.h"
+#include "common/scratch_buffer.h"
 #include "video_core/engines/engine_interface.h"
 
 namespace Core {
@@ -234,9 +236,9 @@ private:
     MemoryManager& memory_manager;
     VideoCore::RasterizerInterface* rasterizer = nullptr;
 
-    std::vector<u8> read_buffer;
-    std::vector<u8> write_buffer;
-    std::vector<u8> intermediate_buffer;
+    Common::ScratchBuffer<u8> read_buffer;
+    Common::ScratchBuffer<u8> write_buffer;
+    Common::ScratchBuffer<u8> intermediate_buffer;
 
     static constexpr std::size_t NUM_REGS = 0x800;
     struct Regs {

+ 3 - 3
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<u32>(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<const u8> 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<size_t>(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];

+ 4 - 3
src/video_core/host1x/vic.h

@@ -4,8 +4,9 @@
 #pragma once
 
 #include <memory>
-#include <vector>
+
 #include "common/common_types.h"
+#include "common/scratch_buffer.h"
 
 struct SwsContext;
 
@@ -49,8 +50,8 @@ private:
     /// size does not change during a stream
     using AVMallocPtr = std::unique_ptr<u8, decltype(&av_free)>;
     AVMallocPtr converted_frame_buffer;
-    std::vector<u8> luma_buffer;
-    std::vector<u8> chroma_buffer;
+    Common::ScratchBuffer<u8> luma_buffer;
+    Common::ScratchBuffer<u8> chroma_buffer;
 
     GPUVAddr config_struct_address{};
     GPUVAddr output_surface_luma_address{};