diff -r dolphin_21/.git/HEAD dolphin_22/.git/HEAD
1c1
< efb97598629b96378edd783af82936185d8a99ce
---
> 7f217a8bb24da323042081a02b65f84f9ce9761f
Binary files dolphin_21/.git/index and dolphin_22/.git/index differ
diff -r dolphin_21/.git/logs/HEAD dolphin_22/.git/logs/HEAD
37d36
< 7f217a8bb24da323042081a02b65f84f9ce9761f efb97598629b96378edd783af82936185d8a99ce zlice <zlice@gfly.psy> 1518727907 -0700	checkout: moving from 7f217a8bb24da323042081a02b65f84f9ce9761f to efb97598629b96378edd783af82936185d8a99ce
diff -r dolphin_21/Source/Core/Common/GL/GLExtensions/GLExtensions.cpp dolphin_22/Source/Core/Common/GL/GLExtensions/GLExtensions.cpp
1872a1873,1875
> 
>     // ARB_get_texture_sub_image
>     GLFUNC_REQUIRES(glGetTextureSubImage, "GL_ARB_get_texture_sub_image !VERSION_4_5"),
diff -r dolphin_21/Source/Core/DolphinQt2/Config/Graphics/AdvancedWidget.cpp dolphin_22/Source/Core/DolphinQt2/Config/Graphics/AdvancedWidget.cpp
65c65
<   m_use_fullres_framedumps = new GraphicsBool(tr("Full Resolution Frame Dumps"),
---
>   m_use_fullres_framedumps = new GraphicsBool(tr("Internal Resolution Frame Dumps"),
126,127d125
<   const auto supports_fr_framedumps = g_Config.backend_info.bSupportsInternalResolutionFrameDumps;
<   m_use_fullres_framedumps->setEnabled(supports_fr_framedumps);
diff -r dolphin_21/Source/Core/DolphinWX/VideoConfigDiag.cpp dolphin_22/Source/Core/DolphinWX/VideoConfigDiag.cpp
846,851c846,848
<       if (vconfig.backend_info.bSupportsInternalResolutionFrameDumps)
<       {
<         szr_utility->Add(CreateCheckBox(page_advanced, _("Full Resolution Frame Dumps"),
<                                         wxGetTranslation(internal_resolution_frame_dumping_desc),
<                                         Config::GFX_INTERNAL_RESOLUTION_FRAME_DUMPS));
<       }
---
>       szr_utility->Add(CreateCheckBox(page_advanced, _("Internal Resolution Frame Dumps"),
>                                       wxGetTranslation(internal_resolution_frame_dumping_desc),
>                                       Config::GFX_INTERNAL_RESOLUTION_FRAME_DUMPS));
diff -r dolphin_21/Source/Core/VideoBackends/D3D/D3DBase.cpp dolphin_22/Source/Core/VideoBackends/D3D/D3DBase.cpp
55,56d54
< bool bFrameInProgress = false;
< 
603,623d600
< }
< 
< bool BeginFrame()
< {
<   if (bFrameInProgress)
<   {
<     PanicAlert("BeginFrame called although a frame is already in progress");
<     return false;
<   }
<   bFrameInProgress = true;
<   return (device != nullptr);
< }
< 
< void EndFrame()
< {
<   if (!bFrameInProgress)
<   {
<     PanicAlert("EndFrame called although no frame is in progress");
<     return;
<   }
<   bFrameInProgress = false;
diff -r dolphin_21/Source/Core/VideoBackends/D3D/D3DBase.h dolphin_22/Source/Core/VideoBackends/D3D/D3DBase.h
66,67d65
< bool BeginFrame();
< void EndFrame();
diff -r dolphin_21/Source/Core/VideoBackends/D3D/D3DState.cpp dolphin_22/Source/Core/VideoBackends/D3D/D3DState.cpp
265a266,280
> StateCache::~StateCache()
> {
>   for (auto& it : m_depth)
>     SAFE_RELEASE(it.second);
> 
>   for (auto& it : m_raster)
>     SAFE_RELEASE(it.second);
> 
>   for (auto& it : m_blend)
>     SAFE_RELEASE(it.second);
> 
>   for (auto& it : m_sampler)
>     SAFE_RELEASE(it.second);
> }
> 
472,498d486
< }
< 
< void StateCache::Clear()
< {
<   for (auto it : m_depth)
<   {
<     SAFE_RELEASE(it.second);
<   }
<   m_depth.clear();
< 
<   for (auto it : m_raster)
<   {
<     SAFE_RELEASE(it.second);
<   }
<   m_raster.clear();
< 
<   for (auto it : m_blend)
<   {
<     SAFE_RELEASE(it.second);
<   }
<   m_blend.clear();
< 
<   for (auto it : m_sampler)
<   {
<     SAFE_RELEASE(it.second);
<   }
<   m_sampler.clear();
diff -r dolphin_21/Source/Core/VideoBackends/D3D/D3DState.h dolphin_22/Source/Core/VideoBackends/D3D/D3DState.h
25a26,27
>   ~StateCache();
> 
32,34d33
< 
<   // Release all cached states and clear hash tables.
<   void Clear();
diff -r dolphin_21/Source/Core/VideoBackends/D3D/DXTexture.cpp dolphin_22/Source/Core/VideoBackends/D3D/DXTexture.cpp
42a43,45
>     return DXGI_FORMAT_R8G8B8A8_UNORM;
>   case AbstractTextureFormat::BGRA8:
>     return DXGI_FORMAT_B8G8R8A8_UNORM;
43a47
>     PanicAlert("Unhandled texture format.");
83d86
<   SAFE_RELEASE(m_staging_texture);
96,166c99,124
< std::optional<AbstractTexture::RawTextureInfo> DXTexture::MapFullImpl()
< {
<   CD3D11_TEXTURE2D_DESC staging_texture_desc(DXGI_FORMAT_R8G8B8A8_UNORM, m_config.width,
<                                              m_config.height, 1, 1, 0, D3D11_USAGE_STAGING,
<                                              D3D11_CPU_ACCESS_READ);
< 
<   HRESULT hr = D3D::device->CreateTexture2D(&staging_texture_desc, nullptr, &m_staging_texture);
<   if (FAILED(hr))
<   {
<     WARN_LOG(VIDEO, "Failed to create texture dumping readback texture: %X", static_cast<u32>(hr));
<     return {};
<   }
< 
<   // Copy the selected data to the staging texture
<   D3D::context->CopyResource(m_staging_texture, m_texture->GetTex());
< 
<   // Map the staging texture to client memory, and encode it as a .png image.
<   D3D11_MAPPED_SUBRESOURCE map;
<   hr = D3D::context->Map(m_staging_texture, 0, D3D11_MAP_READ, 0, &map);
<   if (FAILED(hr))
<   {
<     WARN_LOG(VIDEO, "Failed to map texture dumping readback texture: %X", static_cast<u32>(hr));
<     return {};
<   }
< 
<   return AbstractTexture::RawTextureInfo{reinterpret_cast<u8*>(map.pData), map.RowPitch,
<                                          m_config.width, m_config.height};
< }
< 
< std::optional<AbstractTexture::RawTextureInfo> DXTexture::MapRegionImpl(u32 level, u32 x, u32 y,
<                                                                         u32 width, u32 height)
< {
<   CD3D11_TEXTURE2D_DESC staging_texture_desc(DXGI_FORMAT_R8G8B8A8_UNORM, width, height, 1, 1, 0,
<                                              D3D11_USAGE_STAGING, D3D11_CPU_ACCESS_READ);
< 
<   HRESULT hr = D3D::device->CreateTexture2D(&staging_texture_desc, nullptr, &m_staging_texture);
<   if (FAILED(hr))
<   {
<     WARN_LOG(VIDEO, "Failed to create texture dumping readback texture: %X", static_cast<u32>(hr));
<     return {};
<   }
< 
<   // Copy the selected data to the staging texture
<   CD3D11_BOX src_box(x, y, 0, width, height, 1);
<   D3D::context->CopySubresourceRegion(m_staging_texture, 0, 0, 0, 0, m_texture->GetTex(),
<                                       D3D11CalcSubresource(level, 0, m_config.levels), &src_box);
< 
<   // Map the staging texture to client memory, and encode it as a .png image.
<   D3D11_MAPPED_SUBRESOURCE map;
<   hr = D3D::context->Map(m_staging_texture, 0, D3D11_MAP_READ, 0, &map);
<   if (FAILED(hr))
<   {
<     WARN_LOG(VIDEO, "Failed to map texture dumping readback texture: %X", static_cast<u32>(hr));
<     return {};
<   }
< 
<   return AbstractTexture::RawTextureInfo{reinterpret_cast<u8*>(map.pData), map.RowPitch,
<                                          m_config.width, m_config.height};
< }
< 
< void DXTexture::Unmap()
< {
<   if (!m_staging_texture)
<     return;
< 
<   D3D::context->Unmap(m_staging_texture, 0);
< }
< 
< void DXTexture::CopyRectangleFromTexture(const AbstractTexture* source,
<                                          const MathUtil::Rectangle<int>& srcrect,
<                                          const MathUtil::Rectangle<int>& dstrect)
---
> void DXTexture::CopyRectangleFromTexture(const AbstractTexture* src,
>                                          const MathUtil::Rectangle<int>& src_rect, u32 src_layer,
>                                          u32 src_level, const MathUtil::Rectangle<int>& dst_rect,
>                                          u32 dst_layer, u32 dst_level)
> {
>   const DXTexture* srcentry = static_cast<const DXTexture*>(src);
>   _assert_(src_rect.GetWidth() == dst_rect.GetWidth() &&
>            src_rect.GetHeight() == dst_rect.GetHeight());
> 
>   D3D11_BOX src_box;
>   src_box.left = src_rect.left;
>   src_box.top = src_rect.top;
>   src_box.right = src_rect.right;
>   src_box.bottom = src_rect.bottom;
>   src_box.front = 0;
>   src_box.back = 1;
> 
>   D3D::context->CopySubresourceRegion(
>       m_texture->GetTex(), D3D11CalcSubresource(dst_level, dst_layer, m_config.levels),
>       dst_rect.left, dst_rect.top, 0, srcentry->m_texture->GetTex(),
>       D3D11CalcSubresource(src_level, src_layer, srcentry->m_config.levels), &src_box);
> }
> 
> void DXTexture::ScaleRectangleFromTexture(const AbstractTexture* source,
>                                           const MathUtil::Rectangle<int>& srcrect,
>                                           const MathUtil::Rectangle<int>& dstrect)
169,177c127
<   if (srcrect.GetWidth() == dstrect.GetWidth() && srcrect.GetHeight() == dstrect.GetHeight())
<   {
<     D3D11_BOX srcbox;
<     srcbox.left = srcrect.left;
<     srcbox.top = srcrect.top;
<     srcbox.right = srcrect.right;
<     srcbox.bottom = srcrect.bottom;
<     srcbox.front = 0;
<     srcbox.back = srcentry->m_config.layers;
---
>   _assert_(m_config.rendertarget);
179,186d128
<     D3D::context->CopySubresourceRegion(m_texture->GetTex(), 0, dstrect.left, dstrect.top, 0,
<                                         srcentry->m_texture->GetTex(), 0, &srcbox);
<     return;
<   }
<   else if (!m_config.rendertarget)
<   {
<     return;
<   }
216c158
<   size_t src_pitch = CalculateHostTextureLevelPitch(m_config.format, row_length);
---
>   size_t src_pitch = CalculateStrideForFormat(m_config.format, row_length);
219a162,293
> 
> DXStagingTexture::DXStagingTexture(StagingTextureType type, const TextureConfig& config,
>                                    ID3D11Texture2D* tex)
>     : AbstractStagingTexture(type, config), m_tex(tex)
> {
> }
> 
> DXStagingTexture::~DXStagingTexture()
> {
>   if (IsMapped())
>     DXStagingTexture::Unmap();
>   SAFE_RELEASE(m_tex);
> }
> 
> std::unique_ptr<DXStagingTexture> DXStagingTexture::Create(StagingTextureType type,
>                                                            const TextureConfig& config)
> {
>   D3D11_USAGE usage;
>   UINT cpu_flags;
>   if (type == StagingTextureType::Readback)
>   {
>     usage = D3D11_USAGE_STAGING;
>     cpu_flags = D3D11_CPU_ACCESS_READ;
>   }
>   else if (type == StagingTextureType::Upload)
>   {
>     usage = D3D11_USAGE_DYNAMIC;
>     cpu_flags = D3D11_CPU_ACCESS_WRITE;
>   }
>   else
>   {
>     usage = D3D11_USAGE_STAGING;
>     cpu_flags = D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE;
>   }
> 
>   CD3D11_TEXTURE2D_DESC desc(GetDXGIFormatForHostFormat(config.format), config.width, config.height,
>                              1, 1, 0, usage, cpu_flags);
> 
>   ID3D11Texture2D* texture;
>   HRESULT hr = D3D::device->CreateTexture2D(&desc, nullptr, &texture);
>   CHECK(SUCCEEDED(hr), "Create staging texture");
>   if (FAILED(hr))
>     return nullptr;
> 
>   return std::unique_ptr<DXStagingTexture>(new DXStagingTexture(type, config, texture));
> }
> 
> void DXStagingTexture::CopyFromTexture(const AbstractTexture* src,
>                                        const MathUtil::Rectangle<int>& src_rect, u32 src_layer,
>                                        u32 src_level, const MathUtil::Rectangle<int>& dst_rect)
> {
>   _assert_(m_type == StagingTextureType::Readback);
>   _assert_(src_rect.GetWidth() == dst_rect.GetWidth() &&
>            src_rect.GetHeight() == dst_rect.GetHeight());
>   _assert_(src_rect.left >= 0 && static_cast<u32>(src_rect.right) <= src->GetConfig().width &&
>            src_rect.top >= 0 && static_cast<u32>(src_rect.bottom) <= src->GetConfig().height);
>   _assert_(dst_rect.left >= 0 && static_cast<u32>(dst_rect.right) <= m_config.width &&
>            dst_rect.top >= 0 && static_cast<u32>(dst_rect.bottom) <= m_config.height);
> 
>   if (IsMapped())
>     DXStagingTexture::Unmap();
> 
>   CD3D11_BOX src_box(src_rect.left, src_rect.top, 0, src_rect.right, src_rect.bottom, 1);
>   D3D::context->CopySubresourceRegion(
>       m_tex, 0, static_cast<u32>(dst_rect.left), static_cast<u32>(dst_rect.top), 0,
>       static_cast<const DXTexture*>(src)->GetRawTexIdentifier()->GetTex(),
>       D3D11CalcSubresource(src_level, src_layer, src->GetConfig().levels), &src_box);
> 
>   m_needs_flush = true;
> }
> 
> void DXStagingTexture::CopyToTexture(const MathUtil::Rectangle<int>& src_rect, AbstractTexture* dst,
>                                      const MathUtil::Rectangle<int>& dst_rect, u32 dst_layer,
>                                      u32 dst_level)
> {
>   _assert_(m_type == StagingTextureType::Upload);
>   _assert_(src_rect.GetWidth() == dst_rect.GetWidth() &&
>            src_rect.GetHeight() == dst_rect.GetHeight());
>   _assert_(src_rect.left >= 0 && static_cast<u32>(src_rect.right) <= m_config.width &&
>            src_rect.top >= 0 && static_cast<u32>(src_rect.bottom) <= m_config.height);
>   _assert_(dst_rect.left >= 0 && static_cast<u32>(dst_rect.right) <= dst->GetConfig().width &&
>            dst_rect.top >= 0 && static_cast<u32>(dst_rect.bottom) <= dst->GetConfig().height);
> 
>   if (IsMapped())
>     DXStagingTexture::Unmap();
> 
>   CD3D11_BOX src_box(src_rect.left, src_rect.top, 0, src_rect.right, src_rect.bottom, 1);
>   D3D::context->CopySubresourceRegion(
>       static_cast<const DXTexture*>(dst)->GetRawTexIdentifier()->GetTex(),
>       D3D11CalcSubresource(dst_level, dst_layer, dst->GetConfig().levels),
>       static_cast<u32>(dst_rect.left), static_cast<u32>(dst_rect.top), 0, m_tex, 0, &src_box);
> }
> 
> bool DXStagingTexture::Map()
> {
>   if (m_map_pointer)
>     return true;
> 
>   D3D11_MAP map_type;
>   if (m_type == StagingTextureType::Readback)
>     map_type = D3D11_MAP_READ;
>   else if (m_type == StagingTextureType::Upload)
>     map_type = D3D11_MAP_WRITE;
>   else
>     map_type = D3D11_MAP_READ_WRITE;
> 
>   D3D11_MAPPED_SUBRESOURCE sr;
>   HRESULT hr = D3D::context->Map(m_tex, 0, map_type, 0, &sr);
>   CHECK(SUCCEEDED(hr), "Map readback texture");
>   if (FAILED(hr))
>     return false;
> 
>   m_map_pointer = reinterpret_cast<char*>(sr.pData);
>   m_map_stride = sr.RowPitch;
>   return true;
> }
> 
> void DXStagingTexture::Unmap()
> {
>   if (!m_map_pointer)
>     return;
> 
>   D3D::context->Unmap(m_tex, 0);
>   m_map_pointer = nullptr;
> }
> 
> void DXStagingTexture::Flush()
> {
>   // Flushing is handled by the API.
>   m_needs_flush = false;
> }
> 
diff -r dolphin_21/Source/Core/VideoBackends/D3D/DXTexture.h dolphin_22/Source/Core/VideoBackends/D3D/DXTexture.h
8a9
> #include "VideoCommon/AbstractStagingTexture.h"
22d22
<   void Unmap() override;
24,26c24,30
<   void CopyRectangleFromTexture(const AbstractTexture* source,
<                                 const MathUtil::Rectangle<int>& srcrect,
<                                 const MathUtil::Rectangle<int>& dstrect) override;
---
>   void CopyRectangleFromTexture(const AbstractTexture* src,
>                                 const MathUtil::Rectangle<int>& src_rect, u32 src_layer,
>                                 u32 src_level, const MathUtil::Rectangle<int>& dst_rect,
>                                 u32 dst_layer, u32 dst_level) override;
>   void ScaleRectangleFromTexture(const AbstractTexture* source,
>                                  const MathUtil::Rectangle<int>& srcrect,
>                                  const MathUtil::Rectangle<int>& dstrect) override;
33,36d36
<   std::optional<RawTextureInfo> MapFullImpl() override;
<   std::optional<RawTextureInfo> MapRegionImpl(u32 level, u32 x, u32 y, u32 width,
<                                               u32 height) override;
< 
38c38,63
<   ID3D11Texture2D* m_staging_texture = nullptr;
---
> };
> 
> class DXStagingTexture final : public AbstractStagingTexture
> {
> public:
>   DXStagingTexture() = delete;
>   ~DXStagingTexture();
> 
>   void CopyFromTexture(const AbstractTexture* src, const MathUtil::Rectangle<int>& src_rect,
>                        u32 src_layer, u32 src_level,
>                        const MathUtil::Rectangle<int>& dst_rect) override;
>   void CopyToTexture(const MathUtil::Rectangle<int>& src_rect, AbstractTexture* dst,
>                      const MathUtil::Rectangle<int>& dst_rect, u32 dst_layer,
>                      u32 dst_level) override;
> 
>   bool Map() override;
>   void Unmap() override;
>   void Flush() override;
> 
>   static std::unique_ptr<DXStagingTexture> Create(StagingTextureType type,
>                                                   const TextureConfig& config);
> 
> private:
>   DXStagingTexture(StagingTextureType type, const TextureConfig& config, ID3D11Texture2D* tex);
> 
>   ID3D11Texture2D* m_tex = nullptr;
diff -r dolphin_21/Source/Core/VideoBackends/D3D/main.cpp dolphin_22/Source/Core/VideoBackends/D3D/main.cpp
78d77
<   g_Config.backend_info.bSupportsInternalResolutionFrameDumps = false;
183a183,184
> 
>   D3D::Close();
diff -r dolphin_21/Source/Core/VideoBackends/D3D/PSTextureEncoder.cpp dolphin_22/Source/Core/VideoBackends/D3D/PSTextureEncoder.cpp
6a7
> #include "Common/Assert.h"
12a14
> #include "VideoBackends/D3D/DXTexture.h"
17a20,21
> #include "VideoCommon/AbstractStagingTexture.h"
> #include "VideoCommon/AbstractTexture.h"
34,35d37
<     : m_ready(false), m_out(nullptr), m_outRTV(nullptr), m_outStage(nullptr),
<       m_encodeParams(nullptr)
38a41,42
> PSTextureEncoder::~PSTextureEncoder() = default;
> 
41,68c45,51
<   m_ready = false;
< 
<   HRESULT hr;
< 
<   // Create output texture RGBA format
<   // TODO: This Texture is overly large and parts of it are unused
<   //       EFB2RAM copies use max (EFB_WIDTH * 4) by (EFB_HEIGHT / 4)
<   //       XFB2RAM copies use max (EFB_WIDTH / 2) by (EFB_HEIGHT)
<   D3D11_TEXTURE2D_DESC t2dd = CD3D11_TEXTURE2D_DESC(DXGI_FORMAT_B8G8R8A8_UNORM, EFB_WIDTH * 4, 1024,
<                                                     1, 1, D3D11_BIND_RENDER_TARGET);
<   hr = D3D::device->CreateTexture2D(&t2dd, nullptr, &m_out);
<   CHECK(SUCCEEDED(hr), "create efb encode output texture");
<   D3D::SetDebugObjectName(m_out, "efb encoder output texture");
< 
<   // Create output render target view
<   D3D11_RENDER_TARGET_VIEW_DESC rtvd = CD3D11_RENDER_TARGET_VIEW_DESC(
<       m_out, D3D11_RTV_DIMENSION_TEXTURE2D, DXGI_FORMAT_B8G8R8A8_UNORM);
<   hr = D3D::device->CreateRenderTargetView(m_out, &rtvd, &m_outRTV);
<   CHECK(SUCCEEDED(hr), "create efb encode output render target view");
<   D3D::SetDebugObjectName(m_outRTV, "efb encoder output rtv");
< 
<   // Create output staging buffer
<   t2dd.Usage = D3D11_USAGE_STAGING;
<   t2dd.BindFlags = 0;
<   t2dd.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
<   hr = D3D::device->CreateTexture2D(&t2dd, nullptr, &m_outStage);
<   CHECK(SUCCEEDED(hr), "create efb encode output staging buffer");
<   D3D::SetDebugObjectName(m_outStage, "efb encoder output staging buffer");
---
>   // TODO: Move this to a constant somewhere in common.
>   TextureConfig encoding_texture_config(EFB_WIDTH * 4, 1024, 1, 1, AbstractTextureFormat::BGRA8,
>                                         true);
>   m_encoding_render_texture = g_renderer->CreateTexture(encoding_texture_config);
>   m_encoding_readback_texture =
>       g_renderer->CreateStagingTexture(StagingTextureType::Readback, encoding_texture_config);
>   _assert_(m_encoding_render_texture && m_encoding_readback_texture);
72c55
<   hr = D3D::device->CreateBuffer(&bd, nullptr, &m_encodeParams);
---
>   HRESULT hr = D3D::device->CreateBuffer(&bd, nullptr, &m_encode_params);
74,76c57
<   D3D::SetDebugObjectName(m_encodeParams, "efb encoder params buffer");
< 
<   m_ready = true;
---
>   D3D::SetDebugObjectName(m_encode_params, "efb encoder params buffer");
81,82d61
<   m_ready = false;
< 
84d62
<   {
86d63
<   }
89,92c66
<   SAFE_RELEASE(m_encodeParams);
<   SAFE_RELEASE(m_outStage);
<   SAFE_RELEASE(m_outRTV);
<   SAFE_RELEASE(m_out);
---
>   SAFE_RELEASE(m_encode_params);
99,103d72
<   if (!m_ready)  // Make sure we initialized OK
<     return;
< 
<   HRESULT hr;
< 
125c94,97
<     D3D::context->OMSetRenderTargets(1, &m_outRTV, nullptr);
---
>     D3D::context->OMSetRenderTargets(
>         1,
>         &static_cast<DXTexture*>(m_encoding_render_texture.get())->GetRawTexIdentifier()->GetRTV(),
>         nullptr);
133,134c105,106
<     D3D::context->UpdateSubresource(m_encodeParams, 0, nullptr, &encode_params, 0, 0);
<     D3D::stateman->SetPixelConstants(m_encodeParams);
---
>     D3D::context->UpdateSubresource(m_encode_params, 0, nullptr, &encode_params, 0, 0);
>     D3D::stateman->SetPixelConstants(m_encode_params);
151,161c123,127
<     D3D11_BOX srcBox = CD3D11_BOX(0, 0, 0, words_per_row, num_blocks_y, 1);
<     D3D::context->CopySubresourceRegion(m_outStage, 0, 0, 0, 0, m_out, 0, &srcBox);
< 
<     // Transfer staging buffer to GameCube/Wii RAM
<     D3D11_MAPPED_SUBRESOURCE map = {0};
<     hr = D3D::context->Map(m_outStage, 0, D3D11_MAP_READ, 0, &map);
<     CHECK(SUCCEEDED(hr), "map staging buffer (0x%x)", hr);
< 
<     u8* src = (u8*)map.pData;
<     u32 readStride = std::min(bytes_per_row, map.RowPitch);
<     for (unsigned int y = 0; y < num_blocks_y; ++y)
---
>     MathUtil::Rectangle<int> copy_rect(0, 0, words_per_row, num_blocks_y);
>     m_encoding_readback_texture->CopyFromTexture(m_encoding_render_texture.get(), copy_rect, 0, 0,
>                                                  copy_rect);
>     m_encoding_readback_texture->Flush();
>     if (m_encoding_readback_texture->Map())
163,165c129,130
<       memcpy(dst, src, readStride);
<       dst += memory_stride;
<       src += map.RowPitch;
---
>       m_encoding_readback_texture->ReadTexels(copy_rect, dst, memory_stride);
>       m_encoding_readback_texture->Unmap();
167,168d131
< 
<     D3D::context->Unmap(m_outStage, 0);
diff -r dolphin_21/Source/Core/VideoBackends/D3D/PSTextureEncoder.h dolphin_22/Source/Core/VideoBackends/D3D/PSTextureEncoder.h
7a8
> #include <memory>
12a14,16
> class AbstractTexture;
> class AbstractStagingTexture;
> 
31a36
>   ~PSTextureEncoder();
42,47c47,49
<   bool m_ready;
< 
<   ID3D11Texture2D* m_out;
<   ID3D11RenderTargetView* m_outRTV;
<   ID3D11Texture2D* m_outStage;
<   ID3D11Buffer* m_encodeParams;
---
>   ID3D11Buffer* m_encode_params = nullptr;
>   std::unique_ptr<AbstractTexture> m_encoding_render_texture;
>   std::unique_ptr<AbstractStagingTexture> m_encoding_readback_texture;
diff -r dolphin_21/Source/Core/VideoBackends/D3D/Render.cpp dolphin_22/Source/Core/VideoBackends/D3D/Render.cpp
56c56
< struct GXPipelineState
---
> Renderer::Renderer() : ::Renderer(D3D::GetBackBufferWidth(), D3D::GetBackBufferHeight())
58,72c58,67
<   std::array<SamplerState, 8> samplers;
<   BlendingState blend;
<   DepthState zmode;
<   RasterizationState raster;
< };
< 
< static u32 s_last_multisamples = 1;
< static bool s_last_stereo_mode = false;
< static bool s_last_fullscreen_mode = false;
< 
< static std::array<ID3D11BlendState*, 4> s_clear_blend_states{};
< static std::array<ID3D11DepthStencilState*, 3> s_clear_depth_states{};
< static ID3D11BlendState* s_reset_blend_state = nullptr;
< static ID3D11DepthStencilState* s_reset_depth_state = nullptr;
< static ID3D11RasterizerState* s_reset_rast_state = nullptr;
---
>   m_last_multisamples = g_ActiveConfig.iMultisamples;
>   m_last_stereo_mode = g_ActiveConfig.stereo_mode != StereoMode::Off;
>   m_last_fullscreen_mode = D3D::GetFullscreenState();
> 
>   g_framebuffer_manager = std::make_unique<FramebufferManager>(m_target_width, m_target_height);
>   SetupDeviceObjects();
> 
>   // Setup GX pipeline state
>   for (auto& sampler : m_gx_state.samplers)
>     sampler.hex = RenderState::GetPointSamplerState().hex;
74,75c69,72
< static ID3D11Texture2D* s_screenshot_texture = nullptr;
< static D3DTexture2D* s_3d_vision_texture = nullptr;
---
>   m_gx_state.zmode.testenable = false;
>   m_gx_state.zmode.updateenable = false;
>   m_gx_state.zmode.func = ZMode::NEVER;
>   m_gx_state.raster.cullmode = GenMode::CULL_NONE;
77,78c74,89
< static GXPipelineState s_gx_state;
< static StateCache s_gx_state_cache;
---
>   // Clear EFB textures
>   constexpr std::array<float, 4> clear_color{{0.f, 0.f, 0.f, 1.f}};
>   D3D::context->ClearRenderTargetView(FramebufferManager::GetEFBColorTexture()->GetRTV(),
>                                       clear_color.data());
>   D3D::context->ClearDepthStencilView(FramebufferManager::GetEFBDepthTexture()->GetDSV(),
>                                       D3D11_CLEAR_DEPTH, 0.f, 0);
> 
>   D3D11_VIEWPORT vp = CD3D11_VIEWPORT(0.f, 0.f, (float)m_target_width, (float)m_target_height);
>   D3D::context->RSSetViewports(1, &vp);
>   FramebufferManager::BindEFBRenderTarget();
> }
> 
> Renderer::~Renderer()
> {
>   TeardownDeviceObjects();
> }
80c91
< static void SetupDeviceObjects()
---
> void Renderer::SetupDeviceObjects()
91c102
<   hr = D3D::device->CreateDepthStencilState(&ddesc, &s_clear_depth_states[0]);
---
>   hr = D3D::device->CreateDepthStencilState(&ddesc, &m_clear_depth_states[0]);
95c106
<   hr = D3D::device->CreateDepthStencilState(&ddesc, &s_clear_depth_states[1]);
---
>   hr = D3D::device->CreateDepthStencilState(&ddesc, &m_clear_depth_states[1]);
98c109
<   hr = D3D::device->CreateDepthStencilState(&ddesc, &s_clear_depth_states[2]);
---
>   hr = D3D::device->CreateDepthStencilState(&ddesc, &m_clear_depth_states[2]);
100c111
<   D3D::SetDebugObjectName(s_clear_depth_states[0],
---
>   D3D::SetDebugObjectName(m_clear_depth_states[0],
103c114
<       s_clear_depth_states[1],
---
>       m_clear_depth_states[1],
106c117
<       s_clear_depth_states[2],
---
>       m_clear_depth_states[2],
120c131
<   hr = D3D::device->CreateBlendState(&blenddesc, &s_reset_blend_state);
---
>   hr = D3D::device->CreateBlendState(&blenddesc, &m_reset_blend_state);
122c133
<   D3D::SetDebugObjectName(s_reset_blend_state, "blend state for Renderer::ResetAPIState");
---
>   D3D::SetDebugObjectName(m_reset_blend_state, "blend state for Renderer::ResetAPIState");
124,125c135,136
<   s_clear_blend_states[0] = s_reset_blend_state;
<   s_reset_blend_state->AddRef();
---
>   m_clear_blend_states[0] = m_reset_blend_state;
>   m_reset_blend_state->AddRef();
129c140
<   hr = D3D::device->CreateBlendState(&blenddesc, &s_clear_blend_states[1]);
---
>   hr = D3D::device->CreateBlendState(&blenddesc, &m_clear_blend_states[1]);
133c144
<   hr = D3D::device->CreateBlendState(&blenddesc, &s_clear_blend_states[2]);
---
>   hr = D3D::device->CreateBlendState(&blenddesc, &m_clear_blend_states[2]);
137c148
<   hr = D3D::device->CreateBlendState(&blenddesc, &s_clear_blend_states[3]);
---
>   hr = D3D::device->CreateBlendState(&blenddesc, &m_clear_blend_states[3]);
146c157
<   hr = D3D::device->CreateDepthStencilState(&ddesc, &s_reset_depth_state);
---
>   hr = D3D::device->CreateDepthStencilState(&ddesc, &m_reset_depth_state);
148c159
<   D3D::SetDebugObjectName(s_reset_depth_state, "depth stencil state for Renderer::ResetAPIState");
---
>   D3D::SetDebugObjectName(m_reset_depth_state, "depth stencil state for Renderer::ResetAPIState");
152c163
<   hr = D3D::device->CreateRasterizerState(&rastdesc, &s_reset_rast_state);
---
>   hr = D3D::device->CreateRasterizerState(&rastdesc, &m_reset_rast_state);
154c165
<   D3D::SetDebugObjectName(s_reset_rast_state, "rasterizer state for Renderer::ResetAPIState");
---
>   D3D::SetDebugObjectName(m_reset_rast_state, "rasterizer state for Renderer::ResetAPIState");
156c167
<   s_screenshot_texture = nullptr;
---
>   m_screenshot_texture = nullptr;
160c171
< static void TeardownDeviceObjects()
---
> void Renderer::TeardownDeviceObjects()
164,177c175,186
<   SAFE_RELEASE(s_clear_blend_states[0]);
<   SAFE_RELEASE(s_clear_blend_states[1]);
<   SAFE_RELEASE(s_clear_blend_states[2]);
<   SAFE_RELEASE(s_clear_blend_states[3]);
<   SAFE_RELEASE(s_clear_depth_states[0]);
<   SAFE_RELEASE(s_clear_depth_states[1]);
<   SAFE_RELEASE(s_clear_depth_states[2]);
<   SAFE_RELEASE(s_reset_blend_state);
<   SAFE_RELEASE(s_reset_depth_state);
<   SAFE_RELEASE(s_reset_rast_state);
<   SAFE_RELEASE(s_screenshot_texture);
<   SAFE_RELEASE(s_3d_vision_texture);
< 
<   s_gx_state_cache.Clear();
---
>   SAFE_RELEASE(m_clear_blend_states[0]);
>   SAFE_RELEASE(m_clear_blend_states[1]);
>   SAFE_RELEASE(m_clear_blend_states[2]);
>   SAFE_RELEASE(m_clear_blend_states[3]);
>   SAFE_RELEASE(m_clear_depth_states[0]);
>   SAFE_RELEASE(m_clear_depth_states[1]);
>   SAFE_RELEASE(m_clear_depth_states[2]);
>   SAFE_RELEASE(m_reset_blend_state);
>   SAFE_RELEASE(m_reset_depth_state);
>   SAFE_RELEASE(m_reset_rast_state);
>   SAFE_RELEASE(m_screenshot_texture);
>   SAFE_RELEASE(m_3d_vision_texture);
180c189
< static void Create3DVisionTexture(int width, int height)
---
> void Renderer::Create3DVisionTexture(int width, int height)
200c209
<   s_3d_vision_texture =
---
>   m_3d_vision_texture =
205c214
< Renderer::Renderer() : ::Renderer(D3D::GetBackBufferWidth(), D3D::GetBackBufferHeight())
---
> std::unique_ptr<AbstractTexture> Renderer::CreateTexture(const TextureConfig& config)
207,233c216
<   s_last_multisamples = g_ActiveConfig.iMultisamples;
<   s_last_stereo_mode = g_ActiveConfig.stereo_mode != StereoMode::Off;
<   s_last_fullscreen_mode = D3D::GetFullscreenState();
< 
<   g_framebuffer_manager = std::make_unique<FramebufferManager>(m_target_width, m_target_height);
<   SetupDeviceObjects();
< 
<   // Setup GX pipeline state
<   for (auto& sampler : s_gx_state.samplers)
<     sampler.hex = RenderState::GetPointSamplerState().hex;
< 
<   s_gx_state.zmode.testenable = false;
<   s_gx_state.zmode.updateenable = false;
<   s_gx_state.zmode.func = ZMode::NEVER;
<   s_gx_state.raster.cullmode = GenMode::CULL_NONE;
< 
<   // Clear EFB textures
<   constexpr std::array<float, 4> clear_color{{0.f, 0.f, 0.f, 1.f}};
<   D3D::context->ClearRenderTargetView(FramebufferManager::GetEFBColorTexture()->GetRTV(),
<                                       clear_color.data());
<   D3D::context->ClearDepthStencilView(FramebufferManager::GetEFBDepthTexture()->GetDSV(),
<                                       D3D11_CLEAR_DEPTH, 0.f, 0);
< 
<   D3D11_VIEWPORT vp = CD3D11_VIEWPORT(0.f, 0.f, (float)m_target_width, (float)m_target_height);
<   D3D::context->RSSetViewports(1, &vp);
<   FramebufferManager::BindEFBRenderTarget();
<   D3D::BeginFrame();
---
>   return std::make_unique<DXTexture>(config);
236c219,220
< Renderer::~Renderer()
---
> std::unique_ptr<AbstractStagingTexture> Renderer::CreateStagingTexture(StagingTextureType type,
>                                                                        const TextureConfig& config)
238,241c222
<   TeardownDeviceObjects();
<   D3D::EndFrame();
<   D3D::Present();
<   D3D::Close();
---
>   return DXStagingTexture::Create(type, config);
446,447c427,428
<     D3D::stateman->PushBlendState(s_clear_blend_states[3]);
<     D3D::stateman->PushDepthState(s_clear_depth_states[1]);
---
>     D3D::stateman->PushBlendState(m_clear_blend_states[3]);
>     D3D::stateman->PushDepthState(m_clear_depth_states[1]);
529c510
<     D3D::stateman->PushBlendState(s_clear_blend_states[0]);
---
>     D3D::stateman->PushBlendState(m_clear_blend_states[0]);
531c512
<     D3D::stateman->PushBlendState(s_clear_blend_states[1]);
---
>     D3D::stateman->PushBlendState(m_clear_blend_states[1]);
533c514
<     D3D::stateman->PushBlendState(s_clear_blend_states[2]);
---
>     D3D::stateman->PushBlendState(m_clear_blend_states[2]);
535c516
<     D3D::stateman->PushBlendState(s_clear_blend_states[3]);
---
>     D3D::stateman->PushBlendState(m_clear_blend_states[3]);
541c522
<     D3D::stateman->PushDepthState(s_clear_depth_states[1]);
---
>     D3D::stateman->PushDepthState(m_clear_depth_states[1]);
543c524
<     D3D::stateman->PushDepthState(s_clear_depth_states[2]);
---
>     D3D::stateman->PushDepthState(m_clear_depth_states[2]);
603c584
<   s_gx_state.blend.hex = state.hex;
---
>   m_gx_state.blend.hex = state.hex;
636d616
<   D3D::EndFrame();
649c629
<   const bool fs_changed = s_last_fullscreen_mode != fullscreen;
---
>   const bool fs_changed = m_last_fullscreen_mode != fullscreen;
656,657c636,637
<       s_last_multisamples != g_ActiveConfig.iMultisamples ||
<       s_last_stereo_mode != (g_ActiveConfig.stereo_mode != StereoMode::Off))
---
>       m_last_multisamples != g_ActiveConfig.iMultisamples ||
>       m_last_stereo_mode != (g_ActiveConfig.stereo_mode != StereoMode::Off))
659,660c639,640
<     s_last_multisamples = g_ActiveConfig.iMultisamples;
<     s_last_fullscreen_mode = fullscreen;
---
>     m_last_multisamples = g_ActiveConfig.iMultisamples;
>     m_last_fullscreen_mode = fullscreen;
667,668c647,648
<       SAFE_RELEASE(s_screenshot_texture);
<       SAFE_RELEASE(s_3d_vision_texture);
---
>       SAFE_RELEASE(m_screenshot_texture);
>       SAFE_RELEASE(m_3d_vision_texture);
675c655
<     s_last_stereo_mode = g_ActiveConfig.stereo_mode != StereoMode::Off;
---
>     m_last_stereo_mode = g_ActiveConfig.stereo_mode != StereoMode::Off;
697d676
<   D3D::BeginFrame();
705,707c684,686
<   D3D::stateman->PushBlendState(s_reset_blend_state);
<   D3D::stateman->PushDepthState(s_reset_depth_state);
<   D3D::stateman->PushRasterizerState(s_reset_rast_state);
---
>   D3D::stateman->PushBlendState(m_reset_blend_state);
>   D3D::stateman->PushDepthState(m_reset_depth_state);
>   D3D::stateman->PushRasterizerState(m_reset_rast_state);
722,724c701,703
<   D3D::stateman->PushBlendState(s_gx_state_cache.Get(s_gx_state.blend));
<   D3D::stateman->PushDepthState(s_gx_state_cache.Get(s_gx_state.zmode));
<   D3D::stateman->PushRasterizerState(s_gx_state_cache.Get(s_gx_state.raster));
---
>   D3D::stateman->PushBlendState(m_state_cache.Get(m_gx_state.blend));
>   D3D::stateman->PushDepthState(m_state_cache.Get(m_gx_state.zmode));
>   D3D::stateman->PushRasterizerState(m_state_cache.Get(m_gx_state.raster));
726,727c705,706
<       StateCache::GetPrimitiveTopology(s_gx_state.raster.primitive));
<   FramebufferManager::SetIntegerEFBRenderTarget(s_gx_state.blend.logicopenable);
---
>       StateCache::GetPrimitiveTopology(m_gx_state.raster.primitive));
>   FramebufferManager::SetIntegerEFBRenderTarget(m_gx_state.blend.logicopenable);
729,730c708,709
<   for (u32 stage = 0; stage < static_cast<u32>(s_gx_state.samplers.size()); stage++)
<     D3D::stateman->SetSampler(stage, s_gx_state_cache.Get(s_gx_state.samplers[stage]));
---
>   for (u32 stage = 0; stage < static_cast<u32>(m_gx_state.samplers.size()); stage++)
>     D3D::stateman->SetSampler(stage, m_state_cache.Get(m_gx_state.samplers[stage]));
749c728
<   s_gx_state.raster.hex = state.hex;
---
>   m_gx_state.raster.hex = state.hex;
754c733
<   s_gx_state.zmode.hex = state.hex;
---
>   m_gx_state.zmode.hex = state.hex;
759c738
<   s_gx_state.samplers[index].hex = state.hex;
---
>   m_gx_state.samplers[index].hex = state.hex;
834c813
<     if (!s_3d_vision_texture)
---
>     if (!m_3d_vision_texture)
843c822
<     D3D::context->OMSetRenderTargets(1, &s_3d_vision_texture->GetRTV(), nullptr);
---
>     D3D::context->OMSetRenderTargets(1, &m_3d_vision_texture->GetRTV(), nullptr);
861c840
<                                         s_3d_vision_texture->GetTex(), 0, &box);
---
>                                         m_3d_vision_texture->GetTex(), 0, &box);
diff -r dolphin_21/Source/Core/VideoBackends/D3D/Render.h dolphin_22/Source/Core/VideoBackends/D3D/Render.h
6a7
> #include <array>
7a9
> #include "VideoBackends/D3D/D3DState.h"
21a24,28
>   StateCache& GetStateCache() { return m_state_cache; }
>   std::unique_ptr<AbstractTexture> CreateTexture(const TextureConfig& config) override;
>   std::unique_ptr<AbstractStagingTexture>
>   CreateStagingTexture(StagingTextureType type, const TextureConfig& config) override;
> 
58a66,77
>   struct GXPipelineState
>   {
>     std::array<SamplerState, 8> samplers;
>     BlendingState blend;
>     DepthState zmode;
>     RasterizationState raster;
>   };
> 
>   void SetupDeviceObjects();
>   void TeardownDeviceObjects();
>   void Create3DVisionTexture(int width, int height);
> 
60a80,95
> 
>   StateCache m_state_cache;
>   GXPipelineState m_gx_state;
> 
>   std::array<ID3D11BlendState*, 4> m_clear_blend_states{};
>   std::array<ID3D11DepthStencilState*, 3> m_clear_depth_states{};
>   ID3D11BlendState* m_reset_blend_state = nullptr;
>   ID3D11DepthStencilState* m_reset_depth_state = nullptr;
>   ID3D11RasterizerState* m_reset_rast_state = nullptr;
> 
>   ID3D11Texture2D* m_screenshot_texture = nullptr;
>   D3DTexture2D* m_3d_vision_texture = nullptr;
> 
>   u32 m_last_multisamples = 1;
>   bool m_last_stereo_mode = false;
>   bool m_last_fullscreen_mode = false;
diff -r dolphin_21/Source/Core/VideoBackends/D3D/TextureCache.cpp dolphin_22/Source/Core/VideoBackends/D3D/TextureCache.cpp
36,40d35
< std::unique_ptr<AbstractTexture> TextureCache::CreateTexture(const TextureConfig& config)
< {
<   return std::make_unique<DXTexture>(config);
< }
< 
diff -r dolphin_21/Source/Core/VideoBackends/D3D/TextureCache.h dolphin_22/Source/Core/VideoBackends/D3D/TextureCache.h
22,23d21
<   std::unique_ptr<AbstractTexture> CreateTexture(const TextureConfig& config) override;
< 
diff -r dolphin_21/Source/Core/VideoBackends/Null/NullBackend.cpp dolphin_22/Source/Core/VideoBackends/Null/NullBackend.cpp
46d45
<   g_Config.backend_info.bSupportsInternalResolutionFrameDumps = false;
diff -r dolphin_21/Source/Core/VideoBackends/Null/NullTexture.cpp dolphin_22/Source/Core/VideoBackends/Null/NullTexture.cpp
17,19c17,25
< void NullTexture::CopyRectangleFromTexture(const AbstractTexture* source,
<                                            const MathUtil::Rectangle<int>& srcrect,
<                                            const MathUtil::Rectangle<int>& dstrect)
---
> void NullTexture::CopyRectangleFromTexture(const AbstractTexture* src,
>                                            const MathUtil::Rectangle<int>& src_rect, u32 src_layer,
>                                            u32 src_level, const MathUtil::Rectangle<int>& dst_rect,
>                                            u32 dst_layer, u32 dst_level)
> {
> }
> void NullTexture::ScaleRectangleFromTexture(const AbstractTexture* source,
>                                             const MathUtil::Rectangle<int>& srcrect,
>                                             const MathUtil::Rectangle<int>& dstrect)
25a32,70
> }
> 
> NullStagingTexture::NullStagingTexture(StagingTextureType type, const TextureConfig& config)
>     : AbstractStagingTexture(type, config)
> {
>   m_texture_buf.resize(m_texel_size * config.width * config.height);
>   m_map_pointer = reinterpret_cast<char*>(m_texture_buf.data());
>   m_map_stride = m_texel_size * config.width;
> }
> 
> NullStagingTexture::~NullStagingTexture() = default;
> 
> void NullStagingTexture::CopyFromTexture(const AbstractTexture* src,
>                                          const MathUtil::Rectangle<int>& src_rect, u32 src_layer,
>                                          u32 src_level, const MathUtil::Rectangle<int>& dst_rect)
> {
>   m_needs_flush = true;
> }
> 
> void NullStagingTexture::CopyToTexture(const MathUtil::Rectangle<int>& src_rect,
>                                        AbstractTexture* dst,
>                                        const MathUtil::Rectangle<int>& dst_rect, u32 dst_layer,
>                                        u32 dst_level)
> {
>   m_needs_flush = true;
> }
> 
> bool NullStagingTexture::Map()
> {
>   return true;
> }
> 
> void NullStagingTexture::Unmap()
> {
> }
> 
> void NullStagingTexture::Flush()
> {
>   m_needs_flush = false;
diff -r dolphin_21/Source/Core/VideoBackends/Null/NullTexture.h dolphin_22/Source/Core/VideoBackends/Null/NullTexture.h
6a7,8
> #include <vector>
> 
8a11
> #include "VideoCommon/AbstractStagingTexture.h"
21,23c24,30
<   void CopyRectangleFromTexture(const AbstractTexture* source,
<                                 const MathUtil::Rectangle<int>& srcrect,
<                                 const MathUtil::Rectangle<int>& dstrect) override;
---
>   void CopyRectangleFromTexture(const AbstractTexture* src,
>                                 const MathUtil::Rectangle<int>& src_rect, u32 src_layer,
>                                 u32 src_level, const MathUtil::Rectangle<int>& dst_rect,
>                                 u32 dst_layer, u32 dst_level) override;
>   void ScaleRectangleFromTexture(const AbstractTexture* source,
>                                  const MathUtil::Rectangle<int>& srcrect,
>                                  const MathUtil::Rectangle<int>& dstrect) override;
25a33,53
> };
> 
> class NullStagingTexture final : public AbstractStagingTexture
> {
> public:
>   explicit NullStagingTexture(StagingTextureType type, const TextureConfig& config);
>   ~NullStagingTexture();
> 
>   void CopyFromTexture(const AbstractTexture* src, const MathUtil::Rectangle<int>& src_rect,
>                        u32 src_layer, u32 src_level,
>                        const MathUtil::Rectangle<int>& dst_rect) override;
>   void CopyToTexture(const MathUtil::Rectangle<int>& src_rect, AbstractTexture* dst,
>                      const MathUtil::Rectangle<int>& dst_rect, u32 dst_layer,
>                      u32 dst_level) override;
> 
>   bool Map() override;
>   void Unmap() override;
>   void Flush() override;
> 
> private:
>   std::vector<u8> m_texture_buf;
diff -r dolphin_21/Source/Core/VideoBackends/Null/Render.cpp dolphin_22/Source/Core/VideoBackends/Null/Render.cpp
6a7
> #include "VideoBackends/Null/NullTexture.h"
21a23,33
> }
> 
> std::unique_ptr<AbstractTexture> Renderer::CreateTexture(const TextureConfig& config)
> {
>   return std::make_unique<NullTexture>(config);
> }
> 
> std::unique_ptr<AbstractStagingTexture> Renderer::CreateStagingTexture(StagingTextureType type,
>                                                                        const TextureConfig& config)
> {
>   return std::make_unique<NullStagingTexture>(type, config);
diff -r dolphin_21/Source/Core/VideoBackends/Null/Render.h dolphin_22/Source/Core/VideoBackends/Null/Render.h
16a17,20
>   std::unique_ptr<AbstractTexture> CreateTexture(const TextureConfig& config) override;
>   std::unique_ptr<AbstractStagingTexture>
>   CreateStagingTexture(StagingTextureType type, const TextureConfig& config) override;
> 
diff -r dolphin_21/Source/Core/VideoBackends/Null/TextureCache.h dolphin_22/Source/Core/VideoBackends/Null/TextureCache.h
38,43d37
< 
< private:
<   std::unique_ptr<AbstractTexture> CreateTexture(const TextureConfig& config) override
<   {
<     return std::make_unique<NullTexture>(config);
<   }
diff -r dolphin_21/Source/Core/VideoBackends/OGL/main.cpp dolphin_22/Source/Core/VideoBackends/OGL/main.cpp
93d92
<   g_Config.backend_info.bSupportsInternalResolutionFrameDumps = true;
diff -r dolphin_21/Source/Core/VideoBackends/OGL/OGLTexture.cpp dolphin_22/Source/Core/VideoBackends/OGL/OGLTexture.cpp
12d11
< #include "VideoBackends/OGL/Render.h"
38a38,40
>     return storage ? GL_RGBA8 : GL_RGBA;
>   case AbstractTextureFormat::BGRA8:
>     return storage ? GL_RGBA8 : GL_BGRA;
39a42
>     PanicAlert("Unhandled texture format.");
49a53,54
>   case AbstractTextureFormat::BGRA8:
>     return GL_BGRA;
60a66
>   case AbstractTextureFormat::BGRA8:
66a73,81
> 
> bool UsePersistentStagingBuffers()
> {
>   // We require ARB_buffer_storage to create the persistent mapped buffer,
>   // ARB_shader_image_load_store for glMemoryBarrier, and ARB_sync to ensure
>   // the GPU has finished the copy before reading the buffer from the CPU.
>   return g_ogl_config.bSupportsGLBufferStorage && g_ogl_config.bSupportsImageLoadStore &&
>          g_ogl_config.bSupportsGLSync;
> }
88c103
<     _assert_(!IsCompressedHostTextureFormat(m_config.format));
---
>     _assert_(!IsCompressedFormat(m_config.format));
102a118,121
> 
>     // We broke the framebuffer binding here, and need to restore it, as the CreateTexture
>     // method is in the base renderer class and can be called by VideoCommon.
>     FramebufferManager::SetFramebuffer(0);
151,178c170,183
< std::optional<AbstractTexture::RawTextureInfo> OGLTexture::MapFullImpl()
< {
<   if (GLInterface->GetMode() != GLInterfaceMode::MODE_OPENGL)
<     return {};
< 
<   m_staging_data.reserve(m_config.width * m_config.height * 4);
<   glActiveTexture(GL_TEXTURE9);
< 
<   glBindTexture(GL_TEXTURE_2D_ARRAY, m_texId);
<   glGetTexImage(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_staging_data.data());
<   OGLTexture::SetStage();
<   return AbstractTexture::RawTextureInfo{reinterpret_cast<u8*>(m_staging_data.data()),
<                                          m_config.width * 4, m_config.width, m_config.height};
< }
< 
< std::optional<AbstractTexture::RawTextureInfo> OGLTexture::MapRegionImpl(u32 level, u32 x, u32 y,
<                                                                          u32 width, u32 height)
< {
<   if (GLInterface->GetMode() != GLInterfaceMode::MODE_OPENGL)
<     return {};
<   m_staging_data.reserve(m_config.width * m_config.height * 4);
<   glActiveTexture(GL_TEXTURE9);
<   glBindTexture(GL_TEXTURE_2D_ARRAY, m_texId);
<   if (g_ogl_config.bSupportTextureSubImage)
<   {
<     glGetTextureSubImage(GL_TEXTURE_2D_ARRAY, level, GLint(x), GLint(y), 0, GLsizei(width),
<                          GLsizei(height), 0, GL_RGBA, GL_UNSIGNED_BYTE, GLsizei(width * height * 4),
<                          m_staging_data.data());
---
> void OGLTexture::CopyRectangleFromTexture(const AbstractTexture* src,
>                                           const MathUtil::Rectangle<int>& src_rect, u32 src_layer,
>                                           u32 src_level, const MathUtil::Rectangle<int>& dst_rect,
>                                           u32 dst_layer, u32 dst_level)
> {
>   const OGLTexture* srcentry = static_cast<const OGLTexture*>(src);
>   _assert_(src_rect.GetWidth() == dst_rect.GetWidth() &&
>            src_rect.GetHeight() == dst_rect.GetHeight());
>   if (g_ogl_config.bSupportsCopySubImage)
>   {
>     glCopyImageSubData(srcentry->m_texId, GL_TEXTURE_2D_ARRAY, src_level, src_rect.left,
>                        src_rect.top, src_layer, m_texId, GL_TEXTURE_2D_ARRAY, dst_level,
>                        dst_rect.left, dst_rect.top, dst_layer, dst_rect.GetWidth(),
>                        dst_rect.GetHeight(), 1);
182,190c187,194
<     MapRegionSlow(level, x, y, width, height);
<   }
<   OGLTexture::SetStage();
<   return AbstractTexture::RawTextureInfo{m_staging_data.data(), width * 4, width, height};
< }
< 
< void OGLTexture::MapRegionSlow(u32 level, u32 x, u32 y, u32 width, u32 height)
< {
<   glActiveTexture(GL_TEXTURE9);
---
>     // If it isn't a single leveled/layered texture, we need to update the framebuffer.
>     bool update_src_framebuffer =
>         srcentry->m_framebuffer == 0 || srcentry->m_config.layers != 0 || src_level != 0;
>     bool update_dst_framebuffer = m_framebuffer == 0 || m_config.layers != 0 || dst_level != 0;
>     if (!m_framebuffer)
>       glGenFramebuffers(1, &m_framebuffer);
>     if (!srcentry->m_framebuffer)
>       glGenFramebuffers(1, &const_cast<OGLTexture*>(srcentry)->m_framebuffer);
192,197c196,201
<   glBindTexture(GL_TEXTURE_2D_ARRAY, m_texId);
<   glGetTexImage(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_staging_data.data());
< 
<   // Now copy the region out of the staging data
< 
<   const u32 partial_stride = width * 4;
---
>     glBindFramebuffer(GL_READ_FRAMEBUFFER, srcentry->m_framebuffer);
>     if (update_src_framebuffer)
>     {
>       glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, srcentry->m_texId,
>                                 src_level, src_layer);
>     }
199,200c203,208
<   std::vector<u8> partial_data;
<   partial_data.resize(partial_stride * height);
---
>     glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_framebuffer);
>     if (update_dst_framebuffer)
>     {
>       glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_texId, dst_level,
>                                 dst_layer);
>     }
202,203c210,212
<   const u32 staging_stride = m_config.width * 4;
<   const u32 x_offset = x * 4;
---
>     glBlitFramebuffer(src_rect.left, src_rect.top, src_rect.right, src_rect.bottom, dst_rect.left,
>                       dst_rect.top, dst_rect.right, dst_rect.bottom, GL_COLOR_BUFFER_BIT,
>                       GL_NEAREST);
205,206c214,223
<   auto staging_location = m_staging_data.begin() + staging_stride * y;
<   auto partial_location = partial_data.begin();
---
>     if (update_src_framebuffer)
>     {
>       FramebufferManager::FramebufferTexture(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
>                                              GL_TEXTURE_2D_ARRAY, srcentry->m_texId, 0);
>     }
>     if (update_dst_framebuffer)
>     {
>       FramebufferManager::FramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
>                                              GL_TEXTURE_2D_ARRAY, m_texId, 0);
>     }
208,213c225
<   for (size_t i = 0; i < height; ++i)
<   {
<     auto starting_location = staging_location + x_offset;
<     std::copy(starting_location, starting_location + partial_stride, partial_location);
<     staging_location += staging_stride;
<     partial_location += partial_stride;
---
>     FramebufferManager::SetFramebuffer(0);
215,217d226
< 
<   // Now swap the region back in for the staging data
<   m_staging_data.swap(partial_data);
219,222c228,230
< 
< void OGLTexture::CopyRectangleFromTexture(const AbstractTexture* source,
<                                           const MathUtil::Rectangle<int>& srcrect,
<                                           const MathUtil::Rectangle<int>& dstrect)
---
> void OGLTexture::ScaleRectangleFromTexture(const AbstractTexture* source,
>                                            const MathUtil::Rectangle<int>& srcrect,
>                                            const MathUtil::Rectangle<int>& dstrect)
225,233c233
<   if (srcrect.GetWidth() == dstrect.GetWidth() && srcrect.GetHeight() == dstrect.GetHeight() &&
<       g_ogl_config.bSupportsCopySubImage)
<   {
<     glCopyImageSubData(srcentry->m_texId, GL_TEXTURE_2D_ARRAY, 0, srcrect.left, srcrect.top, 0,
<                        m_texId, GL_TEXTURE_2D_ARRAY, 0, dstrect.left, dstrect.top, 0,
<                        dstrect.GetWidth(), dstrect.GetHeight(), srcentry->m_config.layers);
<     return;
<   }
<   else if (!m_framebuffer)
---
>   if (!m_framebuffer)
272c272
<   if (IsCompressedHostTextureFormat(m_config.format))
---
>   if (IsCompressedFormat(m_config.format))
315a316,573
> }
> 
> OGLStagingTexture::OGLStagingTexture(StagingTextureType type, const TextureConfig& config,
>                                      GLenum target, GLuint buffer_name, size_t buffer_size,
>                                      char* map_ptr, size_t map_stride)
>     : AbstractStagingTexture(type, config), m_target(target), m_buffer_name(buffer_name),
>       m_buffer_size(buffer_size)
> {
>   m_map_pointer = map_ptr;
>   m_map_stride = map_stride;
> }
> 
> OGLStagingTexture::~OGLStagingTexture()
> {
>   if (m_fence != 0)
>     glDeleteSync(m_fence);
>   if (m_map_pointer)
>   {
>     glBindBuffer(GL_PIXEL_PACK_BUFFER, m_buffer_name);
>     glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
>     glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
>   }
>   if (m_buffer_name != 0)
>     glDeleteBuffers(1, &m_buffer_name);
> }
> 
> std::unique_ptr<OGLStagingTexture> OGLStagingTexture::Create(StagingTextureType type,
>                                                              const TextureConfig& config)
> {
>   size_t stride = config.GetStride();
>   size_t buffer_size = stride * config.height;
>   GLenum target =
>       type == StagingTextureType::Readback ? GL_PIXEL_PACK_BUFFER : GL_PIXEL_UNPACK_BUFFER;
>   GLuint buffer;
>   glGenBuffers(1, &buffer);
>   glBindBuffer(target, buffer);
> 
>   // Prefer using buffer_storage where possible. This allows us to skip the map/unmap steps.
>   char* buffer_ptr;
>   if (UsePersistentStagingBuffers())
>   {
>     GLenum buffer_flags;
>     GLenum map_flags;
>     if (type == StagingTextureType::Readback)
>     {
>       buffer_flags = GL_MAP_READ_BIT | GL_MAP_PERSISTENT_BIT;
>       map_flags = GL_MAP_READ_BIT | GL_MAP_PERSISTENT_BIT;
>     }
>     else if (type == StagingTextureType::Upload)
>     {
>       buffer_flags = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT;
>       map_flags = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_FLUSH_EXPLICIT_BIT;
>     }
>     else
>     {
>       buffer_flags = GL_MAP_READ_BIT | GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT;
>       map_flags = GL_MAP_READ_BIT | GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT;
>     }
> 
>     glBufferStorage(target, buffer_size, nullptr, buffer_flags);
>     buffer_ptr =
>         reinterpret_cast<char*>(glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, buffer_size, map_flags));
>     _assert_(buffer_ptr != nullptr);
>   }
>   else
>   {
>     // Otherwise, fallback to mapping the buffer each time.
>     glBufferData(target, buffer_size, nullptr,
>                  type == StagingTextureType::Readback ? GL_STREAM_READ : GL_STREAM_DRAW);
>     buffer_ptr = nullptr;
>   }
>   glBindBuffer(target, 0);
> 
>   return std::unique_ptr<OGLStagingTexture>(
>       new OGLStagingTexture(type, config, target, buffer, buffer_size, buffer_ptr, stride));
> }
> 
> void OGLStagingTexture::CopyFromTexture(const AbstractTexture* src,
>                                         const MathUtil::Rectangle<int>& src_rect, u32 src_layer,
>                                         u32 src_level, const MathUtil::Rectangle<int>& dst_rect)
> {
>   _assert_(m_type == StagingTextureType::Readback);
>   _assert_(src_rect.GetWidth() == dst_rect.GetWidth() &&
>            src_rect.GetHeight() == dst_rect.GetHeight());
>   _assert_(src_rect.left >= 0 && static_cast<u32>(src_rect.right) <= src->GetConfig().width &&
>            src_rect.top >= 0 && static_cast<u32>(src_rect.bottom) <= src->GetConfig().height);
>   _assert_(dst_rect.left >= 0 && static_cast<u32>(dst_rect.right) <= m_config.width &&
>            dst_rect.top >= 0 && static_cast<u32>(dst_rect.bottom) <= m_config.height);
> 
>   // Unmap the buffer before writing when not using persistent mappings.
>   if (!UsePersistentStagingBuffers())
>     OGLStagingTexture::Unmap();
> 
>   // Copy from the texture object to the staging buffer.
>   glBindBuffer(GL_PIXEL_PACK_BUFFER, m_buffer_name);
>   glPixelStorei(GL_PACK_ROW_LENGTH, m_config.width);
> 
>   const OGLTexture* gltex = static_cast<const OGLTexture*>(src);
>   size_t dst_offset = dst_rect.top * m_config.GetStride() + dst_rect.left * m_texel_size;
> 
>   // If we don't have a FBO associated with this texture, we need to use a slow path.
>   if (gltex->GetFramebuffer() != 0 && src_layer == 0 && src_level == 0)
>   {
>     // This texture has a framebuffer, so we can use glReadPixels().
>     glBindFramebuffer(GL_READ_FRAMEBUFFER, gltex->GetFramebuffer());
>     glReadPixels(src_rect.left, src_rect.top, src_rect.GetWidth(), src_rect.GetHeight(),
>                  GetGLFormatForTextureFormat(m_config.format),
>                  GetGLTypeForTextureFormat(m_config.format), reinterpret_cast<void*>(dst_offset));
> 
>     // Reset both read/draw framebuffers.
>     glBindFramebuffer(GL_FRAMEBUFFER, FramebufferManager::GetEFBFramebuffer());
>   }
>   else
>   {
>     glActiveTexture(GL_TEXTURE9);
>     glBindTexture(GL_TEXTURE_2D_ARRAY, gltex->GetRawTexIdentifier());
>     if (g_ogl_config.bSupportsTextureSubImage)
>     {
>       glGetTextureSubImage(
>           GL_TEXTURE_2D_ARRAY, src_level, src_rect.left, src_rect.top, src_layer,
>           src_rect.GetWidth(), src_rect.GetHeight(), 1,
>           GetGLFormatForTextureFormat(m_config.format), GetGLTypeForTextureFormat(m_config.format),
>           static_cast<GLsizei>(m_buffer_size - dst_offset), reinterpret_cast<void*>(dst_offset));
>     }
>     else
>     {
>       // TODO: Investigate whether it's faster to use glReadPixels() with a framebuffer, since we're
>       // copying the whole texture, which may waste bandwidth. So we're trading CPU work in creating
>       // the framebuffer for GPU work in copying potentially redundant texels.
>       glGetTexImage(GL_TEXTURE_2D_ARRAY, src_level, GetGLFormatForTextureFormat(m_config.format),
>                     GetGLTypeForTextureFormat(m_config.format), nullptr);
>     }
> 
>     OGLTexture::SetStage();
>   }
> 
>   glPixelStorei(GL_PACK_ROW_LENGTH, 0);
>   glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
> 
>   // If we support buffer storage, create a fence for synchronization.
>   if (UsePersistentStagingBuffers())
>   {
>     if (m_fence != 0)
>       glDeleteSync(m_fence);
> 
>     glMemoryBarrier(GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT);
>     m_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
>   }
> 
>   m_needs_flush = true;
> }
> 
> void OGLStagingTexture::CopyToTexture(const MathUtil::Rectangle<int>& src_rect,
>                                       AbstractTexture* dst,
>                                       const MathUtil::Rectangle<int>& dst_rect, u32 dst_layer,
>                                       u32 dst_level)
> {
>   _assert_(m_type == StagingTextureType::Upload);
>   _assert_(src_rect.GetWidth() == dst_rect.GetWidth() &&
>            src_rect.GetHeight() == dst_rect.GetHeight());
>   _assert_(src_rect.left >= 0 && static_cast<u32>(src_rect.right) <= m_config.width &&
>            src_rect.top >= 0 && static_cast<u32>(src_rect.bottom) <= m_config.height);
>   _assert_(dst_rect.left >= 0 && static_cast<u32>(dst_rect.right) <= dst->GetConfig().width &&
>            dst_rect.top >= 0 && static_cast<u32>(dst_rect.bottom) <= dst->GetConfig().height);
> 
>   size_t src_offset = src_rect.top * m_config.GetStride() + src_rect.left * m_texel_size;
>   size_t copy_size = src_rect.GetHeight() * m_config.GetStride();
> 
>   glBindBuffer(GL_PIXEL_UNPACK_BUFFER, m_buffer_name);
>   glPixelStorei(GL_UNPACK_ROW_LENGTH, m_config.width);
> 
>   if (!UsePersistentStagingBuffers())
>   {
>     // Unmap the buffer before writing when not using persistent mappings.
>     if (m_map_pointer)
>     {
>       glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
>       m_map_pointer = nullptr;
>     }
>   }
>   else
>   {
>     // Since we're not using coherent mapping, we must flush the range explicitly.
>     if (m_type == StagingTextureType::Upload)
>       glFlushMappedBufferRange(GL_PIXEL_UNPACK_BUFFER, src_offset, copy_size);
>     glMemoryBarrier(GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT);
>   }
> 
>   // Copy from the staging buffer to the texture object.
>   glActiveTexture(GL_TEXTURE9);
>   glBindTexture(GL_TEXTURE_2D_ARRAY, static_cast<const OGLTexture*>(dst)->GetRawTexIdentifier());
>   glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, dst_rect.left, dst_rect.top, dst_layer,
>                   dst_rect.GetWidth(), dst_rect.GetHeight(), 1,
>                   GetGLFormatForTextureFormat(m_config.format),
>                   GetGLTypeForTextureFormat(m_config.format), reinterpret_cast<void*>(src_offset));
>   OGLTexture::SetStage();
> 
>   glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
> 
>   // If we support buffer storage, create a fence for synchronization.
>   if (UsePersistentStagingBuffers())
>   {
>     if (m_fence != 0)
>       glDeleteSync(m_fence);
> 
>     m_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
>   }
> 
>   m_needs_flush = true;
> }
> 
> void OGLStagingTexture::Flush()
> {
>   // No-op when not using buffer storage, as the transfers happen on Map().
>   // m_fence will always be zero in this case.
>   if (m_fence == 0)
>   {
>     m_needs_flush = false;
>     return;
>   }
> 
>   glClientWaitSync(m_fence, GL_SYNC_FLUSH_COMMANDS_BIT, GL_TIMEOUT_IGNORED);
>   glDeleteSync(m_fence);
>   m_fence = 0;
>   m_needs_flush = false;
> }
> 
> bool OGLStagingTexture::Map()
> {
>   if (m_map_pointer)
>     return true;
> 
>   // Slow path, map the texture, unmap it later.
>   GLenum flags;
>   if (m_type == StagingTextureType::Readback)
>     flags = GL_MAP_READ_BIT;
>   else if (m_type == StagingTextureType::Upload)
>     flags = GL_MAP_WRITE_BIT;
>   else
>     flags = GL_MAP_READ_BIT | GL_MAP_WRITE_BIT;
>   glBindBuffer(m_target, m_buffer_name);
>   m_map_pointer = reinterpret_cast<char*>(glMapBufferRange(m_target, 0, m_buffer_size, flags));
>   if (!m_map_pointer)
>     return false;
> 
>   return true;
> }
> 
> void OGLStagingTexture::Unmap()
> {
>   // No-op with persistent mapped buffers.
>   if (!m_map_pointer || UsePersistentStagingBuffers())
>     return;
> 
>   glBindBuffer(m_target, m_buffer_name);
>   glUnmapBuffer(m_target);
>   glBindBuffer(m_target, 0);
>   m_map_pointer = nullptr;
diff -r dolphin_21/Source/Core/VideoBackends/OGL/OGLTexture.h dolphin_22/Source/Core/VideoBackends/OGL/OGLTexture.h
10a11
> #include "VideoCommon/AbstractStagingTexture.h"
23,25c24,30
<   void CopyRectangleFromTexture(const AbstractTexture* source,
<                                 const MathUtil::Rectangle<int>& srcrect,
<                                 const MathUtil::Rectangle<int>& dstrect) override;
---
>   void CopyRectangleFromTexture(const AbstractTexture* src,
>                                 const MathUtil::Rectangle<int>& src_rect, u32 src_layer,
>                                 u32 src_level, const MathUtil::Rectangle<int>& dst_rect,
>                                 u32 dst_layer, u32 dst_level) override;
>   void ScaleRectangleFromTexture(const AbstractTexture* source,
>                                  const MathUtil::Rectangle<int>& srcrect,
>                                  const MathUtil::Rectangle<int>& dstrect) override;
36,40d40
<   std::optional<RawTextureInfo> MapFullImpl() override;
<   std::optional<RawTextureInfo> MapRegionImpl(u32 level, u32 x, u32 y, u32 width,
<                                               u32 height) override;
<   void MapRegionSlow(u32 level, u32 x, u32 y, u32 width, u32 height);
< 
43c43,73
<   std::vector<u8> m_staging_data;
---
> };
> 
> class OGLStagingTexture final : public AbstractStagingTexture
> {
> public:
>   OGLStagingTexture() = delete;
>   ~OGLStagingTexture();
> 
>   void CopyFromTexture(const AbstractTexture* src, const MathUtil::Rectangle<int>& src_rect,
>                        u32 src_layer, u32 src_level,
>                        const MathUtil::Rectangle<int>& dst_rect) override;
>   void CopyToTexture(const MathUtil::Rectangle<int>& src_rect, AbstractTexture* dst,
>                      const MathUtil::Rectangle<int>& dst_rect, u32 dst_layer,
>                      u32 dst_level) override;
> 
>   bool Map() override;
>   void Unmap() override;
>   void Flush() override;
> 
>   static std::unique_ptr<OGLStagingTexture> Create(StagingTextureType type,
>                                                    const TextureConfig& config);
> 
> private:
>   OGLStagingTexture(StagingTextureType type, const TextureConfig& config, GLenum target,
>                     GLuint buffer_name, size_t buffer_size, char* map_ptr, size_t map_stride);
> 
> private:
>   GLenum m_target;
>   GLuint m_buffer_name;
>   size_t m_buffer_size;
>   GLsync m_fence = 0;
diff -r dolphin_21/Source/Core/VideoBackends/OGL/Render.cpp dolphin_22/Source/Core/VideoBackends/OGL/Render.cpp
461c461
<   g_ogl_config.bSupportTextureSubImage = GLExtensions::Supports("ARB_get_texture_sub_image");
---
>   g_ogl_config.bSupportsTextureSubImage = GLExtensions::Supports("ARB_get_texture_sub_image");
625a626,627
>       if (GLExtensions::Version() >= 450)
>         g_ogl_config.bSupportsTextureSubImage = true;
814a817,827
> }
> 
> std::unique_ptr<AbstractTexture> Renderer::CreateTexture(const TextureConfig& config)
> {
>   return std::make_unique<OGLTexture>(config);
> }
> 
> std::unique_ptr<AbstractStagingTexture> Renderer::CreateStagingTexture(StagingTextureType type,
>                                                                        const TextureConfig& config)
> {
>   return OGLStagingTexture::Create(type, config);
diff -r dolphin_21/Source/Core/VideoBackends/OGL/Render.h dolphin_22/Source/Core/VideoBackends/OGL/Render.h
61c61
<   bool bSupportTextureSubImage;
---
>   bool bSupportsTextureSubImage;
78a79,82
> 
>   std::unique_ptr<AbstractTexture> CreateTexture(const TextureConfig& config) override;
>   std::unique_ptr<AbstractStagingTexture>
>   CreateStagingTexture(StagingTextureType type, const TextureConfig& config) override;
diff -r dolphin_21/Source/Core/VideoBackends/OGL/TextureCache.cpp dolphin_22/Source/Core/VideoBackends/OGL/TextureCache.cpp
36,40d35
< std::unique_ptr<AbstractTexture> TextureCache::CreateTexture(const TextureConfig& config)
< {
<   return std::make_unique<OGLTexture>(config);
< }
< 
diff -r dolphin_21/Source/Core/VideoBackends/OGL/TextureCache.h dolphin_22/Source/Core/VideoBackends/OGL/TextureCache.h
62d61
<   std::unique_ptr<AbstractTexture> CreateTexture(const TextureConfig& config) override;
101,103d99
< 
< bool SaveTexture(const std::string& filename, u32 textarget, u32 tex, int virtual_width,
<                  int virtual_height, unsigned int level);
diff -r dolphin_21/Source/Core/VideoBackends/OGL/TextureConverter.cpp dolphin_22/Source/Core/VideoBackends/OGL/TextureConverter.cpp
37,39c37,38
< static GLuint s_texConvFrameBuffer[2] = {0, 0};
< static GLuint s_srcTexture = 0;  // for decoding from RAM
< static GLuint s_dstTexture = 0;  // for encoding to RAM
---
> std::unique_ptr<AbstractTexture> s_encoding_render_texture;
> std::unique_ptr<AbstractStagingTexture> s_encoding_readback_texture;
52,53d50
< static GLuint s_PBO = 0;  // for readback with different strides
< 
90,107c87,91
<   glGenFramebuffers(2, s_texConvFrameBuffer);
< 
<   glActiveTexture(GL_TEXTURE9);
<   glGenTextures(1, &s_srcTexture);
<   glBindTexture(GL_TEXTURE_2D, s_srcTexture);
<   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
< 
<   glGenTextures(1, &s_dstTexture);
<   glBindTexture(GL_TEXTURE_2D, s_dstTexture);
<   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
<   glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, renderBufferWidth, renderBufferHeight, 0, GL_RGBA,
<                GL_UNSIGNED_BYTE, nullptr);
< 
<   FramebufferManager::SetFramebuffer(s_texConvFrameBuffer[0]);
<   glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, s_dstTexture, 0);
<   FramebufferManager::SetFramebuffer(0);
< 
<   glGenBuffers(1, &s_PBO);
---
>   TextureConfig config(renderBufferWidth, renderBufferHeight, 1, 1, AbstractTextureFormat::BGRA8,
>                        true);
>   s_encoding_render_texture = g_renderer->CreateTexture(config);
>   s_encoding_readback_texture =
>       g_renderer->CreateStagingTexture(StagingTextureType::Readback, config);
112,115c96,97
<   glDeleteTextures(1, &s_srcTexture);
<   glDeleteTextures(1, &s_dstTexture);
<   glDeleteBuffers(1, &s_PBO);
<   glDeleteFramebuffers(2, s_texConvFrameBuffer);
---
>   s_encoding_readback_texture.reset();
>   s_encoding_render_texture.reset();
120,125d101
< 
<   s_srcTexture = 0;
<   s_dstTexture = 0;
<   s_PBO = 0;
<   s_texConvFrameBuffer[0] = 0;
<   s_texConvFrameBuffer[1] = 0;
133,135c109,110
<   // switch to texture converter frame buffer
<   // attach render buffer as color destination
<   FramebufferManager::SetFramebuffer(s_texConvFrameBuffer[0]);
---
>   FramebufferManager::SetFramebuffer(
>       static_cast<OGLTexture*>(s_encoding_render_texture.get())->GetFramebuffer());
156,165c131,134
<   int dstSize = dst_line_size * dstHeight;
< 
<   // When the dst_line_size and writeStride are the same, we could use glReadPixels directly to RAM.
<   // But instead we always copy the data via a PBO, because macOS inexplicably prefers this (most
<   // noticeably in the Super Mario Sunshine transition).
<   glBindBuffer(GL_PIXEL_PACK_BUFFER, s_PBO);
<   glBufferData(GL_PIXEL_PACK_BUFFER, dstSize, nullptr, GL_STREAM_READ);
<   glReadPixels(0, 0, (GLsizei)(dst_line_size / 4), (GLsizei)dstHeight, GL_BGRA, GL_UNSIGNED_BYTE,
<                nullptr);
<   u8* pbo = (u8*)glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, dstSize, GL_MAP_READ_BIT);
---
>   MathUtil::Rectangle<int> copy_rect(0, 0, dst_line_size / 4, dstHeight);
>   s_encoding_readback_texture->CopyFromTexture(s_encoding_render_texture.get(), copy_rect, 0, 0,
>                                                copy_rect);
>   s_encoding_readback_texture->ReadTexels(copy_rect, destAddr, writeStride);
167,182c136,137
<   if (dst_line_size == writeStride)
<   {
<     memcpy(destAddr, pbo, dst_line_size * dstHeight);
<   }
<   else
<   {
<     for (size_t i = 0; i < dstHeight; ++i)
<     {
<       memcpy(destAddr, pbo, dst_line_size);
<       pbo += dst_line_size;
<       destAddr += writeStride;
<     }
<   }
< 
<   glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
<   glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
---
>   FramebufferManager::SetFramebuffer(0);
>   OGLTexture::SetStage();
diff -r dolphin_21/Source/Core/VideoBackends/Software/SWmain.cpp dolphin_22/Source/Core/VideoBackends/Software/SWmain.cpp
71d70
<   g_Config.backend_info.bSupportsInternalResolutionFrameDumps = false;
diff -r dolphin_21/Source/Core/VideoBackends/Software/SWRenderer.cpp dolphin_22/Source/Core/VideoBackends/Software/SWRenderer.cpp
16a17
> #include "VideoBackends/Software/SWTexture.h"
34a36,46
> }
> 
> std::unique_ptr<AbstractTexture> SWRenderer::CreateTexture(const TextureConfig& config)
> {
>   return std::make_unique<SW::SWTexture>(config);
> }
> 
> std::unique_ptr<AbstractStagingTexture>
> SWRenderer::CreateStagingTexture(StagingTextureType type, const TextureConfig& config)
> {
>   return std::make_unique<SW::SWStagingTexture>(type, config);
diff -r dolphin_21/Source/Core/VideoBackends/Software/SWRenderer.h dolphin_22/Source/Core/VideoBackends/Software/SWRenderer.h
18a19,22
>   std::unique_ptr<AbstractTexture> CreateTexture(const TextureConfig& config) override;
>   std::unique_ptr<AbstractStagingTexture>
>   CreateStagingTexture(StagingTextureType type, const TextureConfig& config) override;
> 
diff -r dolphin_21/Source/Core/VideoBackends/Software/SWTexture.cpp dolphin_22/Source/Core/VideoBackends/Software/SWTexture.cpp
7a8
> #include "Common/Assert.h"
23a25,46
> 
> void CopyTextureData(const TextureConfig& src_config, const u8* src_ptr, u32 src_x, u32 src_y,
>                      u32 width, u32 height, const TextureConfig& dst_config, u8* dst_ptr, u32 dst_x,
>                      u32 dst_y)
> {
>   size_t texel_size = AbstractTexture::GetTexelSizeForFormat(src_config.format);
>   size_t src_stride = src_config.GetStride();
>   size_t src_offset =
>       static_cast<size_t>(src_y) * src_stride + static_cast<size_t>(src_x) * texel_size;
>   size_t dst_stride = dst_config.GetStride();
>   size_t dst_offset =
>       static_cast<size_t>(dst_y) * dst_stride + static_cast<size_t>(dst_x) * texel_size;
>   size_t copy_len = static_cast<size_t>(width) * texel_size;
> 
>   src_ptr += src_offset;
>   dst_ptr += dst_offset;
>   for (u32 i = 0; i < height; i++)
>   {
>     std::memcpy(dst_ptr, src_ptr, copy_len);
>     src_ptr += src_stride;
>     dst_ptr += dst_stride;
>   }
24a48,49
> }
> 
34,36c59,71
< void SWTexture::CopyRectangleFromTexture(const AbstractTexture* source,
<                                          const MathUtil::Rectangle<int>& srcrect,
<                                          const MathUtil::Rectangle<int>& dstrect)
---
> void SWTexture::CopyRectangleFromTexture(const AbstractTexture* src,
>                                          const MathUtil::Rectangle<int>& src_rect, u32 src_layer,
>                                          u32 src_level, const MathUtil::Rectangle<int>& dst_rect,
>                                          u32 dst_layer, u32 dst_level)
> {
>   _assert_(src_level == 0 && src_layer == 0 && dst_layer == 0 && dst_level == 0);
>   CopyTextureData(src->GetConfig(), static_cast<const SWTexture*>(src)->m_data.data(),
>                   src_rect.left, src_rect.top, src_rect.GetWidth(), src_rect.GetHeight(), m_config,
>                   m_data.data(), dst_rect.left, dst_rect.top);
> }
> void SWTexture::ScaleRectangleFromTexture(const AbstractTexture* source,
>                                           const MathUtil::Rectangle<int>& srcrect,
>                                           const MathUtil::Rectangle<int>& dstrect)
75c110,142
< std::optional<AbstractTexture::RawTextureInfo> SWTexture::MapFullImpl()
---
> SWStagingTexture::SWStagingTexture(StagingTextureType type, const TextureConfig& config)
>     : AbstractStagingTexture(type, config)
> {
>   m_data.resize(m_texel_size * config.width * config.height);
>   m_map_pointer = reinterpret_cast<char*>(m_data.data());
>   m_map_stride = m_texel_size * config.width;
> }
> 
> SWStagingTexture::~SWStagingTexture() = default;
> 
> void SWStagingTexture::CopyFromTexture(const AbstractTexture* src,
>                                        const MathUtil::Rectangle<int>& src_rect, u32 src_layer,
>                                        u32 src_level, const MathUtil::Rectangle<int>& dst_rect)
> {
>   _assert_(src_level == 0 && src_layer == 0);
>   CopyTextureData(src->GetConfig(), static_cast<const SWTexture*>(src)->GetData(), src_rect.left,
>                   src_rect.top, src_rect.GetWidth(), src_rect.GetHeight(), m_config, m_data.data(),
>                   dst_rect.left, dst_rect.top);
>   m_needs_flush = true;
> }
> 
> void SWStagingTexture::CopyToTexture(const MathUtil::Rectangle<int>& src_rect, AbstractTexture* dst,
>                                      const MathUtil::Rectangle<int>& dst_rect, u32 dst_layer,
>                                      u32 dst_level)
> {
>   _assert_(dst_level == 0 && dst_layer == 0);
>   CopyTextureData(m_config, m_data.data(), src_rect.left, src_rect.top, src_rect.GetWidth(),
>                   src_rect.GetHeight(), dst->GetConfig(), static_cast<SWTexture*>(dst)->GetData(),
>                   dst_rect.left, dst_rect.top);
>   m_needs_flush = true;
> }
> 
> bool SWStagingTexture::Map()
77,78c144
<   return AbstractTexture::RawTextureInfo{GetData(), m_config.width * 4, m_config.width,
<                                          m_config.height};
---
>   return true;
80a147,154
> void SWStagingTexture::Unmap()
> {
> }
> 
> void SWStagingTexture::Flush()
> {
>   m_needs_flush = false;
> }
diff -r dolphin_21/Source/Core/VideoBackends/Software/SWTexture.h dolphin_22/Source/Core/VideoBackends/Software/SWTexture.h
10a11
> #include "VideoCommon/AbstractStagingTexture.h"
23,25c24,30
<   void CopyRectangleFromTexture(const AbstractTexture* source,
<                                 const MathUtil::Rectangle<int>& srcrect,
<                                 const MathUtil::Rectangle<int>& dstrect) override;
---
>   void CopyRectangleFromTexture(const AbstractTexture* src,
>                                 const MathUtil::Rectangle<int>& src_rect, u32 src_layer,
>                                 u32 src_level, const MathUtil::Rectangle<int>& dst_rect,
>                                 u32 dst_layer, u32 dst_level) override;
>   void ScaleRectangleFromTexture(const AbstractTexture* source,
>                                  const MathUtil::Rectangle<int>& srcrect,
>                                  const MathUtil::Rectangle<int>& dstrect) override;
33c38,39
<   std::optional<RawTextureInfo> MapFullImpl() override;
---
>   std::vector<u8> m_data;
> };
34a41,58
> class SWStagingTexture final : public AbstractStagingTexture
> {
> public:
>   explicit SWStagingTexture(StagingTextureType type, const TextureConfig& config);
>   ~SWStagingTexture();
> 
>   void CopyFromTexture(const AbstractTexture* src, const MathUtil::Rectangle<int>& src_rect,
>                        u32 src_layer, u32 src_level,
>                        const MathUtil::Rectangle<int>& dst_rect) override;
>   void CopyToTexture(const MathUtil::Rectangle<int>& src_rect, AbstractTexture* dst,
>                      const MathUtil::Rectangle<int>& dst_rect, u32 dst_layer,
>                      u32 dst_level) override;
> 
>   bool Map() override;
>   void Unmap() override;
>   void Flush() override;
> 
> private:
diff -r dolphin_21/Source/Core/VideoBackends/Software/TextureCache.h dolphin_22/Source/Core/VideoBackends/Software/TextureCache.h
28,32d27
<   std::unique_ptr<AbstractTexture> CreateTexture(const TextureConfig& config) override
<   {
<     return std::make_unique<SWTexture>(config);
<   }
< 
diff -r dolphin_21/Source/Core/VideoBackends/Vulkan/CMakeLists.txt dolphin_22/Source/Core/VideoBackends/Vulkan/CMakeLists.txt
14d13
<   StagingTexture2D.cpp
diff -r dolphin_21/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp dolphin_22/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp
373a374,379
>   for (auto iter = m_fence_point_callbacks.begin(); iter != m_fence_point_callbacks.end();)
>   {
>     auto backup_iter = iter++;
>     backup_iter->second.second(resources.fence);
>   }
> 
diff -r dolphin_21/Source/Core/VideoBackends/Vulkan/FramebufferManager.cpp dolphin_22/Source/Core/VideoBackends/Vulkan/FramebufferManager.cpp
19d18
< #include "VideoBackends/Vulkan/StagingTexture2D.h"
701c700
<   m_color_readback_texture->ReadTexel(x, y, &value, sizeof(value));
---
>   m_color_readback_texture->ReadTexel(x, y, &value);
714d712
<   VkImageAspectFlags src_aspect = VK_IMAGE_ASPECT_COLOR_BIT;
753,755c751,753
<   m_color_readback_texture->CopyFromImage(g_command_buffer_mgr->GetCurrentCommandBuffer(),
<                                           src_texture->GetImage(), src_aspect, 0, 0, EFB_WIDTH,
<                                           EFB_HEIGHT, 0, 0);
---
>   static_cast<VKStagingTexture*>(m_color_readback_texture.get())
>       ->CopyFromTexture(src_texture, m_color_readback_texture->GetConfig().GetRect(), 0, 0,
>                         m_color_readback_texture->GetConfig().GetRect());
765,770c763
<   Util::ExecuteCurrentCommandsAndRestoreState(false, true);
< 
<   // Map to host memory.
<   if (!m_color_readback_texture->IsMapped() && !m_color_readback_texture->Map())
<     return false;
< 
---
>   m_color_readback_texture->Flush();
781c774
<   m_depth_readback_texture->ReadTexel(x, y, &value, sizeof(value));
---
>   m_depth_readback_texture->ReadTexel(x, y, &value);
794d786
<   VkImageAspectFlags src_aspect = VK_IMAGE_ASPECT_DEPTH_BIT;
799d790
<     src_aspect = VK_IMAGE_ASPECT_COLOR_BIT;
831d821
<     src_aspect = VK_IMAGE_ASPECT_COLOR_BIT;
837,839c827,829
<   m_depth_readback_texture->CopyFromImage(g_command_buffer_mgr->GetCurrentCommandBuffer(),
<                                           src_texture->GetImage(), src_aspect, 0, 0, EFB_WIDTH,
<                                           EFB_HEIGHT, 0, 0);
---
>   static_cast<VKStagingTexture*>(m_depth_readback_texture.get())
>       ->CopyFromTexture(src_texture, m_depth_readback_texture->GetConfig().GetRect(), 0, 0,
>                         m_depth_readback_texture->GetConfig().GetRect());
849,854c839
<   Util::ExecuteCurrentCommandsAndRestoreState(false, true);
< 
<   // Map to host memory.
<   if (!m_depth_readback_texture->IsMapped() && !m_depth_readback_texture->Map())
<     return false;
< 
---
>   m_depth_readback_texture->Flush();
1014,1021d998
<   m_color_readback_texture = StagingTexture2D::Create(STAGING_BUFFER_TYPE_READBACK, EFB_WIDTH,
<                                                       EFB_HEIGHT, EFB_COLOR_TEXTURE_FORMAT);
<   if (!m_color_copy_texture || !m_color_readback_texture)
<   {
<     ERROR_LOG(VIDEO, "Failed to create EFB color readback texture");
<     return false;
<   }
< 
1028,1030c1005
<   m_depth_readback_texture = StagingTexture2D::Create(STAGING_BUFFER_TYPE_READBACK, EFB_WIDTH,
<                                                       EFB_HEIGHT, EFB_DEPTH_TEXTURE_FORMAT);
<   if (!m_depth_copy_texture || !m_depth_readback_texture)
---
>   if (!m_color_copy_texture || !m_depth_copy_texture)
1032c1007
<     ERROR_LOG(VIDEO, "Failed to create EFB depth readback texture");
---
>     ERROR_LOG(VIDEO, "Failed to create EFB copy textures");
1036,1037c1011,1017
<   // With Vulkan, we can leave these textures mapped and use invalidate/flush calls instead.
<   if (!m_color_readback_texture->Map() || !m_depth_readback_texture->Map())
---
>   TextureConfig readback_texture_config(EFB_WIDTH, EFB_HEIGHT, 1, 1, AbstractTextureFormat::RGBA8,
>                                         false);
>   m_color_readback_texture =
>       g_renderer->CreateStagingTexture(StagingTextureType::Mutable, readback_texture_config);
>   m_depth_readback_texture =
>       g_renderer->CreateStagingTexture(StagingTextureType::Mutable, readback_texture_config);
>   if (!m_color_readback_texture || !m_depth_readback_texture)
1039c1019
<     ERROR_LOG(VIDEO, "Failed to map EFB readback textures");
---
>     ERROR_LOG(VIDEO, "Failed to create EFB readback textures");
1116c1096
<     m_color_readback_texture->WriteTexel(x, y, &color, sizeof(color));
---
>     m_color_readback_texture->WriteTexel(x, y, &color);
1129c1109
<     m_depth_readback_texture->WriteTexel(x, y, &depth, sizeof(depth));
---
>     m_depth_readback_texture->WriteTexel(x, y, &depth);
diff -r dolphin_21/Source/Core/VideoBackends/Vulkan/FramebufferManager.h dolphin_22/Source/Core/VideoBackends/Vulkan/FramebufferManager.h
15a16,17
> class AbstractStagingTexture;
> 
18d19
< class StagingTexture2D;
141,142c142,143
<   std::unique_ptr<StagingTexture2D> m_color_readback_texture;
<   std::unique_ptr<StagingTexture2D> m_depth_readback_texture;
---
>   std::unique_ptr<AbstractStagingTexture> m_color_readback_texture;
>   std::unique_ptr<AbstractStagingTexture> m_depth_readback_texture;
diff -r dolphin_21/Source/Core/VideoBackends/Vulkan/Renderer.cpp dolphin_22/Source/Core/VideoBackends/Vulkan/Renderer.cpp
161a162,172
> std::unique_ptr<AbstractTexture> Renderer::CreateTexture(const TextureConfig& config)
> {
>   return VKTexture::Create(config);
> }
> 
> std::unique_ptr<AbstractStagingTexture> Renderer::CreateStagingTexture(StagingTextureType type,
>                                                                        const TextureConfig& config)
> {
>   return VKStagingTexture::Create(type, config);
> }
> 
603d613
<   TargetRectangle source_rc = xfb_texture->GetConfig().GetRect();
diff -r dolphin_21/Source/Core/VideoBackends/Vulkan/Renderer.h dolphin_22/Source/Core/VideoBackends/Vulkan/Renderer.h
34a35,38
>   std::unique_ptr<AbstractTexture> CreateTexture(const TextureConfig& config) override;
>   std::unique_ptr<AbstractStagingTexture>
>   CreateStagingTexture(StagingTextureType type, const TextureConfig& config) override;
> 
diff -r dolphin_21/Source/Core/VideoBackends/Vulkan/StagingBuffer.h dolphin_22/Source/Core/VideoBackends/Vulkan/StagingBuffer.h
62d61
< protected:
66a66
> protected:
Only in dolphin_21/Source/Core/VideoBackends/Vulkan: StagingTexture2D.cpp
Only in dolphin_21/Source/Core/VideoBackends/Vulkan: StagingTexture2D.h
diff -r dolphin_21/Source/Core/VideoBackends/Vulkan/TextureCache.cpp dolphin_22/Source/Core/VideoBackends/Vulkan/TextureCache.cpp
181,185d180
< std::unique_ptr<AbstractTexture> TextureCache::CreateTexture(const TextureConfig& config)
< {
<   return VKTexture::Create(config);
< }
< 
diff -r dolphin_21/Source/Core/VideoBackends/Vulkan/TextureCache.h dolphin_22/Source/Core/VideoBackends/Vulkan/TextureCache.h
34,35d33
<   std::unique_ptr<AbstractTexture> CreateTexture(const TextureConfig& config) override;
< 
diff -r dolphin_21/Source/Core/VideoBackends/Vulkan/TextureConverter.cpp dolphin_22/Source/Core/VideoBackends/Vulkan/TextureConverter.cpp
22d21
< #include "VideoBackends/Vulkan/StagingTexture2D.h"
70,72d68
<   if (m_encoding_render_framebuffer != VK_NULL_HANDLE)
<     vkDestroyFramebuffer(g_vulkan_context->GetDevice(), m_encoding_render_framebuffer, nullptr);
< 
114,119d109
<   if (!CreateEncodingDownloadTexture())
<   {
<     PanicAlert("Failed to create download texture");
<     return false;
<   }
< 
248,249c238,241
<   m_encoding_render_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
<                                                 VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
---
>   static_cast<VKTexture*>(m_encoding_render_texture.get())
>       ->GetRawTexIdentifier()
>       ->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
>                            VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
279c271,272
<   draw.BeginRenderPass(m_encoding_render_framebuffer, render_region);
---
>   draw.BeginRenderPass(static_cast<VKTexture*>(m_encoding_render_texture.get())->GetFramebuffer(),
>                        render_region);
283,295c276,279
<   // Transition the image before copying
<   m_encoding_render_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
<                                                 VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
<   m_encoding_download_texture->CopyFromImage(
<       g_command_buffer_mgr->GetCurrentCommandBuffer(), m_encoding_render_texture->GetImage(),
<       VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, render_width, render_height, 0, 0);
< 
<   // Block until the GPU has finished copying to the staging texture.
<   Util::ExecuteCurrentCommandsAndRestoreState(false, true);
< 
<   // Copy from staging texture to the final destination, adjusting pitch if necessary.
<   m_encoding_download_texture->ReadTexels(0, 0, render_width, render_height, dest_ptr,
<                                           memory_stride);
---
>   MathUtil::Rectangle<int> copy_rect(0, 0, render_width, render_height);
>   m_encoding_readback_texture->CopyFromTexture(m_encoding_render_texture.get(), copy_rect, 0, 0,
>                                                copy_rect);
>   m_encoding_readback_texture->ReadTexels(copy_rect, dest_ptr, memory_stride);
307,308c291,293
<   m_encoding_render_texture->TransitionToLayout(command_buffer,
<                                                 VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
---
>   static_cast<VKTexture*>(m_encoding_render_texture.get())
>       ->GetRawTexIdentifier()
>       ->TransitionToLayout(command_buffer, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
320c305,306
<   draw.BeginRenderPass(m_encoding_render_framebuffer, region);
---
>   draw.BeginRenderPass(static_cast<VKTexture*>(m_encoding_render_texture.get())->GetFramebuffer(),
>                        region);
328,331d313
<   // Render pass transitions to TRANSFER_SRC.
<   m_encoding_render_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
<                                                 VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
< 
333,339c315,318
<   m_encoding_download_texture->CopyFromImage(command_buffer, m_encoding_render_texture->GetImage(),
<                                              VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, output_width,
<                                              dst_height, 0, 0);
<   Util::ExecuteCurrentCommandsAndRestoreState(false, true);
< 
<   // Finally, copy to guest memory. This may have a different stride.
<   m_encoding_download_texture->ReadTexels(0, 0, output_width, dst_height, dst_ptr, dst_stride);
---
>   MathUtil::Rectangle<int> copy_rect(0, 0, output_width, dst_height);
>   m_encoding_readback_texture->CopyFromTexture(m_encoding_render_texture.get(), copy_rect, 0, 0,
>                                                copy_rect);
>   m_encoding_readback_texture->ReadTexels(copy_rect, dst_ptr, dst_stride);
737,740c716,719
<       {0, ENCODING_TEXTURE_FORMAT, VK_SAMPLE_COUNT_1_BIT, VK_ATTACHMENT_LOAD_OP_DONT_CARE,
<        VK_ATTACHMENT_STORE_OP_STORE, VK_ATTACHMENT_LOAD_OP_DONT_CARE,
<        VK_ATTACHMENT_STORE_OP_DONT_CARE, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
<        VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}};
---
>       {0, Util::GetVkFormatForHostTextureFormat(ENCODING_TEXTURE_FORMAT), VK_SAMPLE_COUNT_1_BIT,
>        VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_STORE,
>        VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE,
>        VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}};
772,797c751,752
<   m_encoding_render_texture = Texture2D::Create(
<       ENCODING_TEXTURE_WIDTH, ENCODING_TEXTURE_HEIGHT, 1, 1, ENCODING_TEXTURE_FORMAT,
<       VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_VIEW_TYPE_2D, VK_IMAGE_TILING_OPTIMAL,
<       VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT |
<           VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT);
<   if (!m_encoding_render_texture)
<     return false;
< 
<   VkImageView framebuffer_attachments[] = {m_encoding_render_texture->GetView()};
<   VkFramebufferCreateInfo framebuffer_info = {VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
<                                               nullptr,
<                                               0,
<                                               m_encoding_render_pass,
<                                               static_cast<u32>(ArraySize(framebuffer_attachments)),
<                                               framebuffer_attachments,
<                                               m_encoding_render_texture->GetWidth(),
<                                               m_encoding_render_texture->GetHeight(),
<                                               m_encoding_render_texture->GetLayers()};
< 
<   VkResult res = vkCreateFramebuffer(g_vulkan_context->GetDevice(), &framebuffer_info, nullptr,
<                                      &m_encoding_render_framebuffer);
<   if (res != VK_SUCCESS)
<   {
<     LOG_VULKAN_ERROR(res, "vkCreateFramebuffer failed: ");
<     return false;
<   }
---
>   TextureConfig config(ENCODING_TEXTURE_WIDTH, ENCODING_TEXTURE_HEIGHT, 1, 1,
>                        ENCODING_TEXTURE_FORMAT, true);
799,806c754,756
<   return true;
< }
< 
< bool TextureConverter::CreateEncodingDownloadTexture()
< {
<   m_encoding_download_texture =
<       StagingTexture2D::Create(STAGING_BUFFER_TYPE_READBACK, ENCODING_TEXTURE_WIDTH,
<                                ENCODING_TEXTURE_HEIGHT, ENCODING_TEXTURE_FORMAT);
---
>   m_encoding_render_texture = g_renderer->CreateTexture(config);
>   m_encoding_readback_texture =
>       g_renderer->CreateStagingTexture(StagingTextureType::Readback, config);
808c758
<   return m_encoding_download_texture && m_encoding_download_texture->Map();
---
>   return m_encoding_render_texture && m_encoding_readback_texture;
diff -r dolphin_21/Source/Core/VideoBackends/Vulkan/TextureConverter.h dolphin_22/Source/Core/VideoBackends/Vulkan/TextureConverter.h
18a19,21
> class AbstractTexture;
> class AbstractStagingTexture;
> 
61c64
<   static const VkFormat ENCODING_TEXTURE_FORMAT = VK_FORMAT_B8G8R8A8_UNORM;
---
>   static const AbstractTextureFormat ENCODING_TEXTURE_FORMAT = AbstractTextureFormat::BGRA8;
78,79d80
<   bool CreateEncodingDownloadTexture();
< 
108a110,111
>   std::unique_ptr<AbstractTexture> m_encoding_render_texture;
>   std::unique_ptr<AbstractStagingTexture> m_encoding_readback_texture;
110,112d112
<   std::unique_ptr<Texture2D> m_encoding_render_texture;
<   VkFramebuffer m_encoding_render_framebuffer = VK_NULL_HANDLE;
<   std::unique_ptr<StagingTexture2D> m_encoding_download_texture;
diff -r dolphin_21/Source/Core/VideoBackends/Vulkan/Util.cpp dolphin_22/Source/Core/VideoBackends/Vulkan/Util.cpp
109a110,114
>     return VK_FORMAT_R8G8B8A8_UNORM;
> 
>   case AbstractTextureFormat::BGRA8:
>     return VK_FORMAT_B8G8R8A8_UNORM;
> 
110a116
>     PanicAlert("Unhandled texture format.");
diff -r dolphin_21/Source/Core/VideoBackends/Vulkan/VKTexture.cpp dolphin_22/Source/Core/VideoBackends/Vulkan/VKTexture.cpp
17c17
< #include "VideoBackends/Vulkan/StagingTexture2D.h"
---
> #include "VideoBackends/Vulkan/StagingBuffer.h"
116c116,119
< std::optional<AbstractTexture::RawTextureInfo> VKTexture::MapFullImpl()
---
> void VKTexture::CopyRectangleFromTexture(const AbstractTexture* src,
>                                          const MathUtil::Rectangle<int>& src_rect, u32 src_layer,
>                                          u32 src_level, const MathUtil::Rectangle<int>& dst_rect,
>                                          u32 dst_layer, u32 dst_level)
118,164c121
<   // No support for optimization of full copy
<   return MapRegionImpl(0, 0, 0, m_config.width, m_config.height);
< }
< 
< std::optional<AbstractTexture::RawTextureInfo> VKTexture::MapRegionImpl(u32 level, u32 x, u32 y,
<                                                                         u32 width, u32 height)
< {
<   m_staging_texture = StagingTexture2D::Create(STAGING_BUFFER_TYPE_READBACK, width, height,
<                                                TEXTURECACHE_TEXTURE_FORMAT);
< 
<   // Transition image to transfer source, and invalidate the current state,
<   // since we'll be executing the command buffer.
<   m_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
<                                 VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
<   StateTracker::GetInstance()->EndRenderPass();
< 
<   // Copy to download buffer.
<   m_staging_texture->CopyFromImage(g_command_buffer_mgr->GetCurrentCommandBuffer(),
<                                    m_texture->GetImage(), VK_IMAGE_ASPECT_COLOR_BIT, x, y, width,
<                                    height, level, 0);
< 
<   // Restore original state of texture.
<   m_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
<                                 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
< 
<   // Block until the GPU has finished copying to the staging texture.
<   Util::ExecuteCurrentCommandsAndRestoreState(false, true);
< 
<   // Map the staging texture so we can copy the contents out.
<   if (!m_staging_texture->Map())
<   {
<     PanicAlert("Failed to map staging texture");
<     return {};
<   }
< 
<   return AbstractTexture::RawTextureInfo{reinterpret_cast<u8*>(m_staging_texture->GetMapPointer()),
<                                          static_cast<u32>(m_staging_texture->GetRowStride()), width,
<                                          height};
< }
< 
< void VKTexture::Unmap()
< {
<   if (!m_staging_texture)
<     return;
< 
<   m_staging_texture->Unmap();
< }
---
>   Texture2D* src_texture = static_cast<const VKTexture*>(src)->GetRawTexIdentifier();
166,169d122
< void VKTexture::CopyTextureRectangle(const MathUtil::Rectangle<int>& dst_rect,
<                                      Texture2D* src_texture,
<                                      const MathUtil::Rectangle<int>& src_rect)
< {
179,187c132,136
<       {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0,
<        src_texture->GetLayers()},        // VkImageSubresourceLayers    srcSubresource
<       {src_rect.left, src_rect.top, 0},  // VkOffset3D                  srcOffset
<       {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0,  // VkImageSubresourceLayers    dstSubresource
<        m_config.layers},
<       {dst_rect.left, dst_rect.top, 0},  // VkOffset3D                  dstOffset
<       {static_cast<uint32_t>(src_rect.GetWidth()), static_cast<uint32_t>(src_rect.GetHeight()),
<        1}  // VkExtent3D                  extent
<   };
---
>       {VK_IMAGE_ASPECT_COLOR_BIT, src_level, src_layer, src_texture->GetLayers()},
>       {src_rect.left, src_rect.top, 0},
>       {VK_IMAGE_ASPECT_COLOR_BIT, dst_level, dst_layer, m_config.layers},
>       {dst_rect.left, dst_rect.top, 0},
>       {static_cast<uint32_t>(src_rect.GetWidth()), static_cast<uint32_t>(src_rect.GetHeight()), 1}};
199a149,154
> 
>   // Ensure both textures remain in the SHADER_READ_ONLY layout so they can be bound.
>   src_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
>                                   VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
>   m_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
>                                 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
202,204c157,159
< void VKTexture::ScaleTextureRectangle(const MathUtil::Rectangle<int>& dst_rect,
<                                       Texture2D* src_texture,
<                                       const MathUtil::Rectangle<int>& src_rect)
---
> void VKTexture::ScaleRectangleFromTexture(const AbstractTexture* source,
>                                           const MathUtil::Rectangle<int>& src_rect,
>                                           const MathUtil::Rectangle<int>& dst_rect)
205a161,162
>   Texture2D* src_texture = static_cast<const VKTexture*>(source)->GetRawTexIdentifier();
> 
238,254d194
< }
< 
< void VKTexture::CopyRectangleFromTexture(const AbstractTexture* source,
<                                          const MathUtil::Rectangle<int>& srcrect,
<                                          const MathUtil::Rectangle<int>& dstrect)
< {
<   auto* raw_source_texture = static_cast<const VKTexture*>(source)->GetRawTexIdentifier();
<   CopyRectangleFromTexture(raw_source_texture, srcrect, dstrect);
< }
< 
< void VKTexture::CopyRectangleFromTexture(Texture2D* source, const MathUtil::Rectangle<int>& srcrect,
<                                          const MathUtil::Rectangle<int>& dstrect)
< {
<   if (srcrect.GetWidth() == dstrect.GetWidth() && srcrect.GetHeight() == dstrect.GetHeight())
<     CopyTextureRectangle(dstrect, source, srcrect);
<   else
<     ScaleTextureRectangle(dstrect, source, srcrect);
257,258c197,198
<   source->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
<                              VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
---
>   src_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
>                                   VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
294c234
<   size_t source_pitch = CalculateHostTextureLevelPitch(m_config.format, row_length);
---
>   size_t source_pitch = CalculateStrideForFormat(m_config.format, row_length);
356a297,516
> }
> 
> VKStagingTexture::VKStagingTexture(StagingTextureType type, const TextureConfig& config,
>                                    std::unique_ptr<StagingBuffer> buffer)
>     : AbstractStagingTexture(type, config), m_staging_buffer(std::move(buffer))
> {
> }
> 
> VKStagingTexture::~VKStagingTexture()
> {
>   if (m_needs_flush)
>     VKStagingTexture::Flush();
> }
> 
> std::unique_ptr<VKStagingTexture> VKStagingTexture::Create(StagingTextureType type,
>                                                            const TextureConfig& config)
> {
>   size_t stride = config.GetStride();
>   size_t buffer_size = stride * static_cast<size_t>(config.height);
> 
>   STAGING_BUFFER_TYPE buffer_type;
>   VkImageUsageFlags buffer_usage;
>   if (type == StagingTextureType::Readback)
>   {
>     buffer_type = STAGING_BUFFER_TYPE_READBACK;
>     buffer_usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT;
>   }
>   else if (type == StagingTextureType::Upload)
>   {
>     buffer_type = STAGING_BUFFER_TYPE_UPLOAD;
>     buffer_usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
>   }
>   else
>   {
>     buffer_type = STAGING_BUFFER_TYPE_READBACK;
>     buffer_usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
>   }
> 
>   VkBuffer buffer;
>   VkDeviceMemory memory;
>   bool coherent;
>   if (!StagingBuffer::AllocateBuffer(buffer_type, buffer_size, buffer_usage, &buffer, &memory,
>                                      &coherent))
>   {
>     return nullptr;
>   }
> 
>   std::unique_ptr<StagingBuffer> staging_buffer =
>       std::make_unique<StagingBuffer>(buffer_type, buffer, memory, buffer_size, coherent);
>   std::unique_ptr<VKStagingTexture> staging_tex = std::unique_ptr<VKStagingTexture>(
>       new VKStagingTexture(type, config, std::move(staging_buffer)));
> 
>   // Use persistent mapping.
>   if (!staging_tex->m_staging_buffer->Map())
>     return nullptr;
>   staging_tex->m_map_pointer = staging_tex->m_staging_buffer->GetMapPointer();
>   staging_tex->m_map_stride = stride;
>   return staging_tex;
> }
> 
> void VKStagingTexture::CopyFromTexture(const AbstractTexture* src,
>                                        const MathUtil::Rectangle<int>& src_rect, u32 src_layer,
>                                        u32 src_level, const MathUtil::Rectangle<int>& dst_rect)
> {
>   _assert_(m_type == StagingTextureType::Readback);
>   _assert_(src_rect.GetWidth() == dst_rect.GetWidth() &&
>            src_rect.GetHeight() == dst_rect.GetHeight());
>   _assert_(src_rect.left >= 0 && static_cast<u32>(src_rect.right) <= src->GetConfig().width &&
>            src_rect.top >= 0 && static_cast<u32>(src_rect.bottom) <= src->GetConfig().height);
>   _assert_(dst_rect.left >= 0 && static_cast<u32>(dst_rect.right) <= m_config.width &&
>            dst_rect.top >= 0 && static_cast<u32>(dst_rect.bottom) <= m_config.height);
> 
>   Texture2D* src_tex = static_cast<const VKTexture*>(src)->GetRawTexIdentifier();
>   CopyFromTexture(src_tex, src_rect, src_layer, src_level, dst_rect);
> }
> 
> void VKStagingTexture::CopyFromTexture(Texture2D* src, const MathUtil::Rectangle<int>& src_rect,
>                                        u32 src_layer, u32 src_level,
>                                        const MathUtil::Rectangle<int>& dst_rect)
> {
>   if (m_needs_flush)
>   {
>     // Drop copy before reusing it.
>     g_command_buffer_mgr->RemoveFencePointCallback(this);
>     m_flush_fence = VK_NULL_HANDLE;
>     m_needs_flush = false;
>   }
> 
>   VkImageLayout old_layout = src->GetLayout();
>   src->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
>                           VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
> 
>   // Issue the image->buffer copy, but delay it for now.
>   VkBufferImageCopy image_copy = {};
>   VkImageAspectFlags aspect =
>       Util::IsDepthFormat(src->GetFormat()) ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
>   image_copy.bufferOffset =
>       static_cast<VkDeviceSize>(static_cast<size_t>(dst_rect.top) * m_config.GetStride() +
>                                 static_cast<size_t>(dst_rect.left) * m_texel_size);
>   image_copy.bufferRowLength = static_cast<u32>(m_config.width);
>   image_copy.bufferImageHeight = 0;
>   image_copy.imageSubresource = {aspect, src_level, src_layer, 1};
>   image_copy.imageOffset = {src_rect.left, src_rect.top, 0};
>   image_copy.imageExtent = {static_cast<u32>(src_rect.GetWidth()),
>                             static_cast<u32>(src_rect.GetHeight()), 1u};
>   vkCmdCopyImageToBuffer(g_command_buffer_mgr->GetCurrentCommandBuffer(), src->GetImage(),
>                          VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, m_staging_buffer->GetBuffer(), 1,
>                          &image_copy);
> 
>   // Restore old source texture layout.
>   src->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), old_layout);
> 
>   m_needs_flush = true;
>   g_command_buffer_mgr->AddFencePointCallback(this,
>                                               [this](VkCommandBuffer buf, VkFence fence) {
>                                                 _assert_(m_needs_flush);
>                                                 m_flush_fence = fence;
>                                               },
>                                               [this](VkFence fence) {
>                                                 m_flush_fence = VK_NULL_HANDLE;
>                                                 m_needs_flush = false;
>                                                 g_command_buffer_mgr->RemoveFencePointCallback(
>                                                     this);
>                                               });
> }
> 
> void VKStagingTexture::CopyToTexture(const MathUtil::Rectangle<int>& src_rect, AbstractTexture* dst,
>                                      const MathUtil::Rectangle<int>& dst_rect, u32 dst_layer,
>                                      u32 dst_level)
> {
>   _assert_(m_type == StagingTextureType::Upload);
>   _assert_(src_rect.GetWidth() == dst_rect.GetWidth() &&
>            src_rect.GetHeight() == dst_rect.GetHeight());
>   _assert_(src_rect.left >= 0 && static_cast<u32>(src_rect.right) <= m_config.width &&
>            src_rect.top >= 0 && static_cast<u32>(src_rect.bottom) <= m_config.height);
>   _assert_(dst_rect.left >= 0 && static_cast<u32>(dst_rect.right) <= dst->GetConfig().width &&
>            dst_rect.top >= 0 && static_cast<u32>(dst_rect.bottom) <= dst->GetConfig().height);
> 
>   if (m_needs_flush)
>   {
>     // Drop copy before reusing it.
>     g_command_buffer_mgr->RemoveFencePointCallback(this);
>     m_flush_fence = VK_NULL_HANDLE;
>     m_needs_flush = false;
>   }
> 
>   // Flush caches before copying.
>   m_staging_buffer->FlushCPUCache();
> 
>   Texture2D* dst_tex = static_cast<const VKTexture*>(dst)->GetRawTexIdentifier();
>   VkImageLayout old_layout = dst_tex->GetLayout();
>   dst_tex->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
>                               VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
> 
>   // Issue the image->buffer copy, but delay it for now.
>   VkBufferImageCopy image_copy = {};
>   image_copy.bufferOffset =
>       static_cast<VkDeviceSize>(static_cast<size_t>(src_rect.top) * m_config.GetStride() +
>                                 static_cast<size_t>(src_rect.left) * m_texel_size);
>   image_copy.bufferRowLength = static_cast<u32>(m_config.width);
>   image_copy.bufferImageHeight = 0;
>   image_copy.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, dst_level, dst_layer, 1};
>   image_copy.imageOffset = {dst_rect.left, dst_rect.top, 0};
>   image_copy.imageExtent = {static_cast<u32>(dst_rect.GetWidth()),
>                             static_cast<u32>(dst_rect.GetHeight()), 1u};
>   vkCmdCopyBufferToImage(g_command_buffer_mgr->GetCurrentCommandBuffer(),
>                          m_staging_buffer->GetBuffer(), dst_tex->GetImage(),
>                          VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_copy);
> 
>   // Restore old source texture layout.
>   dst_tex->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), old_layout);
> 
>   m_needs_flush = true;
>   g_command_buffer_mgr->AddFencePointCallback(this,
>                                               [this](VkCommandBuffer buf, VkFence fence) {
>                                                 _assert_(m_needs_flush);
>                                                 m_flush_fence = fence;
>                                               },
>                                               [this](VkFence fence) {
>                                                 m_flush_fence = VK_NULL_HANDLE;
>                                                 m_needs_flush = false;
>                                                 g_command_buffer_mgr->RemoveFencePointCallback(
>                                                     this);
>                                               });
> }
> 
> bool VKStagingTexture::Map()
> {
>   // Always mapped.
>   return true;
> }
> 
> void VKStagingTexture::Unmap()
> {
>   // Always mapped.
> }
> 
> void VKStagingTexture::Flush()
> {
>   if (!m_needs_flush)
>     return;
> 
>   // Either of the below two calls will cause the callback to fire.
>   g_command_buffer_mgr->RemoveFencePointCallback(this);
>   if (m_flush_fence != VK_NULL_HANDLE)
>   {
>     // WaitForFence should fire the callback.
>     g_command_buffer_mgr->WaitForFence(m_flush_fence);
>   }
>   else
>   {
>     // We don't have a fence, and are pending. That means the readback is in the current
>     // command buffer, and must execute it to populate the staging texture.
>     Util::ExecuteCurrentCommandsAndRestoreState(false, true);
>   }
>   m_needs_flush = false;
> 
>   // For readback textures, invalidate the CPU cache as there is new data there.
>   if (m_type == StagingTextureType::Readback)
>     m_staging_buffer->InvalidateCPUCache();
diff -r dolphin_21/Source/Core/VideoBackends/Vulkan/VKTexture.h dolphin_22/Source/Core/VideoBackends/Vulkan/VKTexture.h
9a10
> #include "VideoCommon/AbstractStagingTexture.h"
13a15
> class StagingBuffer;
23d24
<   void Unmap() override;
25,29c26,34
<   void CopyRectangleFromTexture(const AbstractTexture* source,
<                                 const MathUtil::Rectangle<int>& srcrect,
<                                 const MathUtil::Rectangle<int>& dstrect) override;
<   void CopyRectangleFromTexture(Texture2D* source, const MathUtil::Rectangle<int>& srcrect,
<                                 const MathUtil::Rectangle<int>& dstrect);
---
>   void CopyRectangleFromTexture(const AbstractTexture* src,
>                                 const MathUtil::Rectangle<int>& src_rect, u32 src_layer,
>                                 u32 src_level, const MathUtil::Rectangle<int>& dst_rect,
>                                 u32 dst_layer, u32 dst_level) override;
>   void ScaleRectangleFromTexture(const AbstractTexture* source,
>                                  const MathUtil::Rectangle<int>& src_rect,
>                                  const MathUtil::Rectangle<int>& dst_rect);
>   void ScaleRectangleFromTexture(Texture2D* src_texture, const MathUtil::Rectangle<int>& src_rect,
>                                  const MathUtil::Rectangle<int>& dst_rect);
42,53d46
<   // Copies the contents of a texture using vkCmdCopyImage
<   void CopyTextureRectangle(const MathUtil::Rectangle<int>& dst_rect, Texture2D* src_texture,
<                             const MathUtil::Rectangle<int>& src_rect);
< 
<   // Copies (and optionally scales) the contents of a texture using a framgent shader.
<   void ScaleTextureRectangle(const MathUtil::Rectangle<int>& dst_rect, Texture2D* src_texture,
<                              const MathUtil::Rectangle<int>& src_rect);
< 
<   std::optional<RawTextureInfo> MapFullImpl() override;
<   std::optional<RawTextureInfo> MapRegionImpl(u32 level, u32 x, u32 y, u32 width,
<                                               u32 height) override;
< 
55d47
<   std::unique_ptr<StagingTexture2D> m_staging_texture;
56a49,81
> };
> 
> class VKStagingTexture final : public AbstractStagingTexture
> {
> public:
>   VKStagingTexture() = delete;
>   ~VKStagingTexture();
> 
>   void CopyFromTexture(const AbstractTexture* src, const MathUtil::Rectangle<int>& src_rect,
>                        u32 src_layer, u32 src_level,
>                        const MathUtil::Rectangle<int>& dst_rect) override;
>   void CopyToTexture(const MathUtil::Rectangle<int>& src_rect, AbstractTexture* dst,
>                      const MathUtil::Rectangle<int>& dst_rect, u32 dst_layer,
>                      u32 dst_level) override;
> 
>   bool Map() override;
>   void Unmap() override;
>   void Flush() override;
> 
>   // This overload is provided for compatibility as we dropped StagingTexture2D.
>   // For now, FramebufferManager relies on them. But we can drop it once we move that to common.
>   void CopyFromTexture(Texture2D* src, const MathUtil::Rectangle<int>& src_rect, u32 src_layer,
>                        u32 src_level, const MathUtil::Rectangle<int>& dst_rect);
> 
>   static std::unique_ptr<VKStagingTexture> Create(StagingTextureType type,
>                                                   const TextureConfig& config);
> 
> private:
>   VKStagingTexture(StagingTextureType type, const TextureConfig& config,
>                    std::unique_ptr<StagingBuffer> buffer);
> 
>   std::unique_ptr<StagingBuffer> m_staging_buffer;
>   VkFence m_flush_fence = VK_NULL_HANDLE;
diff -r dolphin_21/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp dolphin_22/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp
236,247c236,246
<   config->backend_info.bSupportsDynamicSamplerIndexing = true;        // Assumed support.
<   config->backend_info.bSupportsInternalResolutionFrameDumps = true;  // Assumed support.
<   config->backend_info.bSupportsPostProcessing = true;                // Assumed support.
<   config->backend_info.bSupportsDualSourceBlend = false;              // Dependent on features.
<   config->backend_info.bSupportsGeometryShaders = false;              // Dependent on features.
<   config->backend_info.bSupportsGSInstancing = false;                 // Dependent on features.
<   config->backend_info.bSupportsBBox = false;                         // Dependent on features.
<   config->backend_info.bSupportsFragmentStoresAndAtomics = false;     // Dependent on features.
<   config->backend_info.bSupportsSSAA = false;                         // Dependent on features.
<   config->backend_info.bSupportsDepthClamp = false;                   // Dependent on features.
<   config->backend_info.bSupportsST3CTextures = false;                 // Dependent on features.
<   config->backend_info.bSupportsBPTCTextures = false;                 // Dependent on features.
---
>   config->backend_info.bSupportsDynamicSamplerIndexing = true;     // Assumed support.
>   config->backend_info.bSupportsPostProcessing = true;             // Assumed support.
>   config->backend_info.bSupportsDualSourceBlend = false;           // Dependent on features.
>   config->backend_info.bSupportsGeometryShaders = false;           // Dependent on features.
>   config->backend_info.bSupportsGSInstancing = false;              // Dependent on features.
>   config->backend_info.bSupportsBBox = false;                      // Dependent on features.
>   config->backend_info.bSupportsFragmentStoresAndAtomics = false;  // Dependent on features.
>   config->backend_info.bSupportsSSAA = false;                      // Dependent on features.
>   config->backend_info.bSupportsDepthClamp = false;                // Dependent on features.
>   config->backend_info.bSupportsST3CTextures = false;              // Dependent on features.
>   config->backend_info.bSupportsBPTCTextures = false;              // Dependent on features.
diff -r dolphin_21/Source/Core/VideoBackends/Vulkan/Vulkan.vcxproj dolphin_22/Source/Core/VideoBackends/Vulkan/Vulkan.vcxproj
49d48
<     <ClCompile Include="StagingTexture2D.cpp" />
75d73
<     <ClInclude Include="StagingTexture2D.h" />
Only in dolphin_22/Source/Core/VideoCommon: AbstractStagingTexture.cpp
Only in dolphin_22/Source/Core/VideoCommon: AbstractStagingTexture.h
diff -r dolphin_21/Source/Core/VideoCommon/AbstractTexture.cpp dolphin_22/Source/Core/VideoCommon/AbstractTexture.cpp
8c8,9
< 
---
> #include "Common/MsgHandler.h"
> #include "VideoCommon/AbstractStagingTexture.h"
10a12
> #include "VideoCommon/RenderBase.h"
23c25,26
<   _assert_(m_config.format == AbstractTextureFormat::RGBA8);
---
>   _assert_(!IsCompressedFormat(m_config.format));
>   _assert_(level < m_config.levels);
25c28,39
<   auto result = level == 0 ? Map() : Map(level);
---
>   // Determine dimensions of image we want to save.
>   u32 level_width = std::max(1u, m_config.width >> level);
>   u32 level_height = std::max(1u, m_config.height >> level);
> 
>   // Use a temporary staging texture for the download. Certainly not optimal,
>   // but this is not a frequently-executed code path..
>   TextureConfig readback_texture_config(level_width, level_height, 1, 1,
>                                         AbstractTextureFormat::RGBA8, false);
>   auto readback_texture =
>       g_renderer->CreateStagingTexture(StagingTextureType::Readback, readback_texture_config);
>   if (!readback_texture)
>     return false;
27,28c41,46
<   if (!result.has_value())
<   {
---
>   // Copy to the readback texture's buffer.
>   readback_texture->CopyFromTexture(this, 0, level);
>   readback_texture->Flush();
> 
>   // Map it so we can encode it to the file.
>   if (!readback_texture->Map())
30d47
<   }
32,33c49,51
<   auto raw_data = result.value();
<   return TextureToPng(raw_data.data, raw_data.stride, filename, raw_data.width, raw_data.height);
---
>   return TextureToPng(reinterpret_cast<const u8*>(readback_texture->GetMappedPointer()),
>                       static_cast<int>(readback_texture->GetMappedStride()), filename, level_width,
>                       level_height);
36c54
< std::optional<AbstractTexture::RawTextureInfo> AbstractTexture::Map()
---
> bool AbstractTexture::IsCompressedFormat(AbstractTextureFormat format)
38c56
<   if (m_currently_mapped)
---
>   switch (format)
40,43c58,62
<     Unmap();
<     m_currently_mapped = false;
<   }
<   auto result = MapFullImpl();
---
>   case AbstractTextureFormat::DXT1:
>   case AbstractTextureFormat::DXT3:
>   case AbstractTextureFormat::DXT5:
>   case AbstractTextureFormat::BPTC:
>     return true;
45,48c64,65
<   if (!result.has_value())
<   {
<     m_currently_mapped = false;
<     return {};
---
>   default:
>     return false;
50,52d66
< 
<   m_currently_mapped = true;
<   return result;
55,56c69
< std::optional<AbstractTexture::RawTextureInfo> AbstractTexture::Map(u32 level, u32 x, u32 y,
<                                                                     u32 width, u32 height)
---
> size_t AbstractTexture::CalculateStrideForFormat(AbstractTextureFormat format, u32 row_length)
58,68c71
<   _assert_(level < m_config.levels);
< 
<   u32 max_level_width = std::max(m_config.width >> level, 1u);
<   u32 max_level_height = std::max(m_config.height >> level, 1u);
< 
<   _assert_(width < max_level_width);
<   _assert_(height < max_level_height);
< 
<   auto result = MapRegionImpl(level, x, y, width, height);
< 
<   if (!result.has_value())
---
>   switch (format)
70,71c73,84
<     m_currently_mapped = false;
<     return {};
---
>   case AbstractTextureFormat::DXT1:
>     return static_cast<size_t>(std::max(1u, row_length / 4)) * 8;
>   case AbstractTextureFormat::DXT3:
>   case AbstractTextureFormat::DXT5:
>   case AbstractTextureFormat::BPTC:
>     return static_cast<size_t>(std::max(1u, row_length / 4)) * 16;
>   case AbstractTextureFormat::RGBA8:
>   case AbstractTextureFormat::BGRA8:
>     return static_cast<size_t>(row_length) * 4;
>   default:
>     PanicAlert("Unhandled texture format.");
>     return 0;
73,100d85
< 
<   m_currently_mapped = true;
<   return result;
< }
< 
< std::optional<AbstractTexture::RawTextureInfo> AbstractTexture::Map(u32 level)
< {
<   _assert_(level < m_config.levels);
< 
<   u32 level_width = std::max(m_config.width >> level, 1u);
<   u32 level_height = std::max(m_config.height >> level, 1u);
< 
<   return Map(level, 0, 0, level_width, level_height);
< }
< 
< void AbstractTexture::Unmap()
< {
< }
< 
< std::optional<AbstractTexture::RawTextureInfo> AbstractTexture::MapFullImpl()
< {
<   return {};
< }
< 
< std::optional<AbstractTexture::RawTextureInfo>
< AbstractTexture::MapRegionImpl(u32 level, u32 x, u32 y, u32 width, u32 height)
< {
<   return {};
103,109c88
< bool AbstractTexture::IsCompressedHostTextureFormat(AbstractTextureFormat format)
< {
<   // This will need to be changed if we add any other uncompressed formats.
<   return format != AbstractTextureFormat::RGBA8;
< }
< 
< size_t AbstractTexture::CalculateHostTextureLevelPitch(AbstractTextureFormat format, u32 row_length)
---
> size_t AbstractTexture::GetTexelSizeForFormat(AbstractTextureFormat format)
114c93
<     return static_cast<size_t>(std::max(1u, row_length / 4)) * 8;
---
>     return 8;
118c97
<     return static_cast<size_t>(std::max(1u, row_length / 4)) * 16;
---
>     return 16;
119a99,100
>   case AbstractTextureFormat::BGRA8:
>     return 4;
121c102,103
<     return static_cast<size_t>(row_length) * 4;
---
>     PanicAlert("Unhandled texture format.");
>     return 0;
diff -r dolphin_21/Source/Core/VideoCommon/AbstractTexture.h dolphin_22/Source/Core/VideoCommon/AbstractTexture.h
8d7
< #include <optional>
19a19
> 
21d20
<   bool Save(const std::string& filename, unsigned int level);
23,38c22,28
<   struct RawTextureInfo
<   {
<     const u8* data;
<     u32 stride;
<     u32 width;
<     u32 height;
<   };
< 
<   std::optional<RawTextureInfo> Map();
<   std::optional<RawTextureInfo> Map(u32 level, u32 x, u32 y, u32 width, u32 height);
<   std::optional<RawTextureInfo> Map(u32 level);
<   virtual void Unmap();
< 
<   virtual void CopyRectangleFromTexture(const AbstractTexture* source,
<                                         const MathUtil::Rectangle<int>& srcrect,
<                                         const MathUtil::Rectangle<int>& dstrect) = 0;
---
>   virtual void CopyRectangleFromTexture(const AbstractTexture* src,
>                                         const MathUtil::Rectangle<int>& src_rect, u32 src_layer,
>                                         u32 src_level, const MathUtil::Rectangle<int>& dst_rect,
>                                         u32 dst_layer, u32 dst_level) = 0;
>   virtual void ScaleRectangleFromTexture(const AbstractTexture* source,
>                                          const MathUtil::Rectangle<int>& srcrect,
>                                          const MathUtil::Rectangle<int>& dstrect) = 0;
42,43c32,36
<   static bool IsCompressedHostTextureFormat(AbstractTextureFormat format);
<   static size_t CalculateHostTextureLevelPitch(AbstractTextureFormat format, u32 row_length);
---
>   bool Save(const std::string& filename, unsigned int level);
> 
>   static bool IsCompressedFormat(AbstractTextureFormat format);
>   static size_t CalculateStrideForFormat(AbstractTextureFormat format, u32 row_length);
>   static size_t GetTexelSizeForFormat(AbstractTextureFormat format);
48,52d40
<   virtual std::optional<RawTextureInfo> MapFullImpl();
<   virtual std::optional<RawTextureInfo> MapRegionImpl(u32 level, u32 x, u32 y, u32 width,
<                                                       u32 height);
<   bool m_currently_mapped = false;
< 
diff -r dolphin_21/Source/Core/VideoCommon/CMakeLists.txt dolphin_22/Source/Core/VideoCommon/CMakeLists.txt
1a2
>   AbstractStagingTexture.cpp
diff -r dolphin_21/Source/Core/VideoCommon/MainBase.cpp dolphin_22/Source/Core/VideoCommon/MainBase.cpp
52c52
<   g_renderer->ExitFramedumping();
---
>   g_renderer->ShutdownFrameDumping();
diff -r dolphin_21/Source/Core/VideoCommon/RenderBase.cpp dolphin_22/Source/Core/VideoCommon/RenderBase.cpp
45a46
> #include "VideoCommon/AbstractStagingTexture.h"
102,110d102
< void Renderer::ExitFramedumping()
< {
<   ShutdownFrameDumping();
<   if (m_frame_dump_thread.joinable())
<     m_frame_dump_thread.join();
< 
<   m_dump_texture.reset();
< }
< 
632,639c624,627
<   if (IsFrameDumping() && m_last_xfb_texture)
<   {
<     FinishFrameData();
<   }
<   else
<   {
<     ShutdownFrameDumping();
<   }
---
>   // Ensure the last frame was written to the dump.
>   // This is required even if frame dumping has stopped, since the frame dump is one frame
>   // behind the renderer.
>   FlushFrameDump();
664a653
> 
666,668c655
<       {
<         DoDumpFrame();
<       }
---
>         DumpCurrentFrame();
698c685
< void Renderer::DoDumpFrame()
---
> void Renderer::DumpCurrentFrame()
700c687,688
<   UpdateFrameDumpTexture();
---
>   // Scale/render to frame dump texture.
>   RenderFrameDump();
702,703c690,713
<   auto result = m_dump_texture->Map();
<   if (result.has_value())
---
>   // Queue a readback for the next frame.
>   QueueFrameDumpReadback();
> }
> 
> void Renderer::RenderFrameDump()
> {
>   int target_width, target_height;
>   if (!g_ActiveConfig.bInternalResolutionFrameDumps)
>   {
>     auto target_rect = GetTargetRectangle();
>     target_width = target_rect.GetWidth();
>     target_height = target_rect.GetHeight();
>   }
>   else
>   {
>     std::tie(target_width, target_height) = CalculateOutputDimensions(
>         m_last_xfb_texture->GetConfig().width, m_last_xfb_texture->GetConfig().height);
>   }
> 
>   // Ensure framebuffer exists (we lazily allocate it in case frame dumping isn't used).
>   // Or, resize texture if it isn't large enough to accommodate the current frame.
>   if (!m_frame_dump_render_texture ||
>       m_frame_dump_render_texture->GetConfig().width != static_cast<u32>(target_width) ||
>       m_frame_dump_render_texture->GetConfig().height == static_cast<u32>(target_height))
705,707c715,733
<     auto raw_data = result.value();
<     DumpFrameData(raw_data.data, raw_data.width, raw_data.height, raw_data.stride,
<                   AVIDump::FetchState(m_last_xfb_ticks));
---
>     // Recreate texture objects. Release before creating so we don't temporarily use twice the RAM.
>     TextureConfig config(target_width, target_height, 1, 1, AbstractTextureFormat::RGBA8, true);
>     m_frame_dump_render_texture.reset();
>     m_frame_dump_render_texture = CreateTexture(config);
>     _assert_(m_frame_dump_render_texture);
>   }
> 
>   // Scaling is likely to occur here, but if possible, do a bit-for-bit copy.
>   if (m_last_xfb_region.GetWidth() != target_width ||
>       m_last_xfb_region.GetHeight() != target_height)
>   {
>     m_frame_dump_render_texture->ScaleRectangleFromTexture(
>         m_last_xfb_texture, m_last_xfb_region, EFBRectangle{0, 0, target_width, target_height});
>   }
>   else
>   {
>     m_frame_dump_render_texture->CopyRectangleFromTexture(
>         m_last_xfb_texture, m_last_xfb_region, 0, 0,
>         EFBRectangle{0, 0, target_width, target_height}, 0, 0);
711c737
< void Renderer::UpdateFrameDumpTexture()
---
> void Renderer::QueueFrameDumpReadback()
713,724c739,771
<   int target_width, target_height;
<   std::tie(target_width, target_height) = CalculateOutputDimensions(
<       m_last_xfb_texture->GetConfig().width, m_last_xfb_texture->GetConfig().height);
<   if (m_dump_texture == nullptr ||
<       m_dump_texture->GetConfig().width != static_cast<u32>(target_width) ||
<       m_dump_texture->GetConfig().height != static_cast<u32>(target_height))
<   {
<     TextureConfig config;
<     config.width = target_width;
<     config.height = target_height;
<     config.rendertarget = true;
<     m_dump_texture = g_texture_cache->CreateTexture(config);
---
>   // Index 0 was just sent to AVI dump. Swap with the second texture.
>   if (m_frame_dump_readback_textures[0])
>     std::swap(m_frame_dump_readback_textures[0], m_frame_dump_readback_textures[1]);
> 
>   std::unique_ptr<AbstractStagingTexture>& rbtex = m_frame_dump_readback_textures[0];
>   if (!rbtex || rbtex->GetConfig() != m_frame_dump_render_texture->GetConfig())
>   {
>     rbtex = CreateStagingTexture(StagingTextureType::Readback,
>                                  m_frame_dump_render_texture->GetConfig());
>   }
> 
>   m_last_frame_state = AVIDump::FetchState(m_last_xfb_ticks);
>   m_last_frame_exported = true;
>   rbtex->CopyFromTexture(m_frame_dump_render_texture.get(), 0, 0);
> }
> 
> void Renderer::FlushFrameDump()
> {
>   if (!m_last_frame_exported)
>     return;
> 
>   // Ensure the previously-queued frame was encoded.
>   FinishFrameData();
> 
>   // Queue encoding of the last frame dumped.
>   std::unique_ptr<AbstractStagingTexture>& rbtex = m_frame_dump_readback_textures[0];
>   rbtex->Flush();
>   if (rbtex->Map())
>   {
>     DumpFrameData(reinterpret_cast<u8*>(rbtex->GetMappedPointer()), rbtex->GetConfig().width,
>                   rbtex->GetConfig().height, static_cast<int>(rbtex->GetMappedStride()),
>                   m_last_frame_state);
>     rbtex->Unmap();
726,727c773,778
<   m_dump_texture->CopyRectangleFromTexture(m_last_xfb_texture, m_last_xfb_region,
<                                            EFBRectangle{0, 0, target_width, target_height});
---
> 
>   m_last_frame_exported = false;
> 
>   // Shutdown frame dumping if it is no longer active.
>   if (!IsFrameDumping())
>     ShutdownFrameDumping();
731a783,785
>   // Ensure the last queued readback has been sent to the encoder.
>   FlushFrameDump();
> 
734a789
>   // Ensure previous frame has been encoded.
735a791,792
> 
>   // Wake thread up, and wait for it to exit.
737a795,796
>   if (m_frame_dump_thread.joinable())
>     m_frame_dump_thread.join();
742c801
<   m_frame_dump_config = FrameDumpConfig{m_last_xfb_texture, data, w, h, stride, state};
---
>   m_frame_dump_config = FrameDumpConfig{data, w, h, stride, state};
751a811
>   // Wake worker thread up.
763d822
<   m_frame_dump_config.texture->Unmap();
diff -r dolphin_21/Source/Core/VideoCommon/RenderBase.h dolphin_22/Source/Core/VideoCommon/RenderBase.h
36a37
> class AbstractStagingTexture;
37a39
> struct TextureConfig;
38a41
> enum class StagingTextureType;
81a85,88
>   virtual std::unique_ptr<AbstractTexture> CreateTexture(const TextureConfig& config) = 0;
>   virtual std::unique_ptr<AbstractStagingTexture>
>   CreateStagingTexture(StagingTextureType type, const TextureConfig& config) = 0;
> 
147c154
<   void ExitFramedumping();
---
>   void ShutdownFrameDumping();
186d192
<   void DoDumpFrame();
188d193
<   void ShutdownFrameDumping();
190d194
<   void UpdateFrameDumpTexture();
208d211
<     AbstractTexture* texture;
215a219,225
>   // Texture used for screenshot/frame dumping
>   std::unique_ptr<AbstractTexture> m_frame_dump_render_texture;
>   std::array<std::unique_ptr<AbstractStagingTexture>, 2> m_frame_dump_readback_textures;
>   AVIDump::Frame m_last_frame_state;
>   bool m_last_frame_exported = false;
> 
>   // Tracking of XFB textures so we don't render duplicate frames.
221,222d230
<   std::unique_ptr<AbstractTexture> m_dump_texture;
< 
235a244,254
> 
>   // Asynchronously encodes the current staging texture to the frame dump.
>   void DumpCurrentFrame();
> 
>   // Fills the frame dump render texture with the current XFB texture.
>   void RenderFrameDump();
> 
>   // Queues the current frame for readback, which will be written to AVI next frame.
>   void QueueFrameDumpReadback();
> 
>   // Asynchronously encodes the specified pointer of frame data to the frame dump.
236a256,260
> 
>   // Ensures all rendered frames are queued for encoding.
>   void FlushFrameDump();
> 
>   // Ensures all encoded frames have been written to the output file.
diff -r dolphin_21/Source/Core/VideoCommon/TextureCacheBase.cpp dolphin_22/Source/Core/VideoCommon/TextureCacheBase.cpp
280,282c280,282
<     new_texture->CopyRectangleFromTexture(entry->texture.get(),
<                                           entry->texture->GetConfig().GetRect(),
<                                           new_texture->GetConfig().GetRect());
---
>     new_texture->ScaleRectangleFromTexture(entry->texture.get(),
>                                            entry->texture->GetConfig().GetRect(),
>                                            new_texture->GetConfig().GetRect());
409c409,413
<         entry_to_update->texture->CopyRectangleFromTexture(entry->texture.get(), srcrect, dstrect);
---
>         for (u32 layer = 0; layer < entry->texture->GetConfig().layers; layer++)
>         {
>           entry_to_update->texture->CopyRectangleFromTexture(entry->texture.get(), srcrect, layer,
>                                                              0, dstrect, layer, 0);
>         }
1369,1378d1372
<         if (static_cast<int>(entry->GetWidth()) == srcrect.GetWidth())
<         {
<           srcrect.right -= 1;
<         }
< 
<         if (static_cast<int>(entry->GetHeight()) == srcrect.GetHeight())
<         {
<           srcrect.bottom -= 1;
<         }
< 
1384,1389c1378
<         if (static_cast<int>(entry_to_update->GetWidth()) == dstrect.GetWidth())
<         {
<           dstrect.right -= 1;
<         }
< 
<         if (static_cast<int>(entry_to_update->GetHeight()) == dstrect.GetHeight())
---
>         for (u32 layer = 0; layer < entry->texture->GetConfig().layers; layer++)
1391c1380,1381
<           dstrect.bottom -= 1;
---
>           entry_to_update->texture->CopyRectangleFromTexture(entry->texture.get(), srcrect, layer,
>                                                              0, dstrect, layer, 0);
1393,1395d1382
< 
<         entry_to_update->texture->CopyRectangleFromTexture(entry->texture.get(), srcrect, dstrect);
< 
2093c2080
<     entry = CreateTexture(config);
---
>     entry = g_renderer->CreateTexture(config);
diff -r dolphin_21/Source/Core/VideoCommon/TextureCacheBase.h dolphin_22/Source/Core/VideoCommon/TextureCacheBase.h
276,277d275
<   virtual std::unique_ptr<AbstractTexture> CreateTexture(const TextureConfig& config) = 0;
< 
diff -r dolphin_21/Source/Core/VideoCommon/TextureConfig.cpp dolphin_22/Source/Core/VideoCommon/TextureConfig.cpp
5a6
> #include "VideoCommon/AbstractTexture.h"
14a16,20
> bool TextureConfig::operator!=(const TextureConfig& o) const
> {
>   return !operator==(o);
> }
> 
17a24,39
> }
> 
> MathUtil::Rectangle<int> TextureConfig::GetMipRect(u32 level) const
> {
>   return {0, 0, static_cast<int>(std::max(width >> level, 1u)),
>           static_cast<int>(std::max(height >> level, 1u))};
> }
> 
> size_t TextureConfig::GetStride() const
> {
>   return AbstractTexture::CalculateStrideForFormat(format, width);
> }
> 
> size_t TextureConfig::GetMipStride(u32 level) const
> {
>   return AbstractTexture::CalculateStrideForFormat(format, std::max(width >> level, 1u));
diff -r dolphin_21/Source/Core/VideoCommon/TextureConfig.h dolphin_22/Source/Core/VideoCommon/TextureConfig.h
15a16
>   BGRA8,
19c20,28
<   BPTC
---
>   BPTC,
>   Undefined
> };
> 
> enum class StagingTextureType
> {
>   Readback,  // Optimize for CPU reads, GPU writes, no CPU writes
>   Upload,    // Optimize for CPU writes, GPU reads, no CPU reads
>   Mutable    // Optimize for CPU reads, GPU writes, allow slow CPU reads
24a34,40
>   constexpr TextureConfig(u32 width_, u32 height_, u32 levels_, u32 layers_,
>                           AbstractTextureFormat format_, bool rendertarget_)
>       : width(width_), height(height_), levels(levels_), layers(layers_), format(format_),
>         rendertarget(rendertarget_)
>   {
>   }
> 
25a42
>   bool operator!=(const TextureConfig& o) const;
26a44,46
>   MathUtil::Rectangle<int> GetMipRect(u32 level) const;
>   size_t GetStride() const;
>   size_t GetMipStride(u32 level) const;
diff -r dolphin_21/Source/Core/VideoCommon/VideoCommon.vcxproj dolphin_22/Source/Core/VideoCommon/VideoCommon.vcxproj
38a39
>     <ClCompile Include="AbstractStagingTexture.cpp" />
98a100
>     <ClInclude Include="AbstractStagingTexture.h" />
diff -r dolphin_21/Source/Core/VideoCommon/VideoCommon.vcxproj.filters dolphin_22/Source/Core/VideoCommon/VideoCommon.vcxproj.filters
193a194,196
>     <ClCompile Include="AbstractStagingTexture.cpp">
>       <Filter>Base</Filter>
>     </ClCompile>
363a367,369
>     </ClInclude>
>     <ClInclude Include="AbstractStagingTexture.h">
>       <Filter>Base</Filter>
diff -r dolphin_21/Source/Core/VideoCommon/VideoConfig.cpp dolphin_22/Source/Core/VideoCommon/VideoConfig.cpp
39d38
<   backend_info.bSupportsInternalResolutionFrameDumps = false;
diff -r dolphin_21/Source/Core/VideoCommon/VideoConfig.h dolphin_22/Source/Core/VideoCommon/VideoConfig.h
222d221
<     bool bSupportsInternalResolutionFrameDumps;
