@@ -1484,6 +1484,20 @@ void CFrame::ParseHotkeys()
Config::SetCurrent(Config::GFX_HACK_SKIP_EFB_COPY_TO_RAM,
!Config::Get(Config::GFX_HACK_SKIP_EFB_COPY_TO_RAM));
}
if (IsHotkey(HK_TOGGLE_XFBCOPIES))
{
OSDChoice = 6;
// Toggle XFB copies between XFB2RAM and XFB2Texture
Config::SetCurrent(Config::GFX_HACK_SKIP_XFB_COPY_TO_RAM,
!Config::Get(Config::GFX_HACK_SKIP_XFB_COPY_TO_RAM));
}
if (IsHotkey(HK_TOGGLE_IMMEDIATE_XFB))
{
OSDChoice = 6;
// Toggle immediate present of xfb
Config::SetCurrent(Config::GFX_HACK_IMMEDIATE_XFB,
!Config::Get(Config::GFX_HACK_IMMEDIATE_XFB));
}
if (IsHotkey(HK_TOGGLE_FOG))
{
OSDChoice = 4;
@@ -77,10 +77,6 @@ SoftwareVideoConfigDialog::SoftwareVideoConfigDialog(wxWindow* parent, const std
label_backend->Disable();
choice_backend->Disable();
}

// xfb
szr_rendering->Add(
new SettingCheckBox(page_general, _("Bypass XFB"), "", Config::GFX_USE_XFB, true));
}

// - info
@@ -201,6 +201,17 @@ static wxString skip_efb_copy_to_ram_desc = wxTRANSLATE(
"Stores EFB Copies exclusively on the GPU, bypassing system memory. Causes graphical defects "
"in a small number of games.\n\nEnabled = EFB Copies to Texture\nDisabled = EFB Copies to RAM "
"(and Texture)\n\nIf unsure, leave this checked.");
static wxString skip_xfb_copy_to_ram_desc = wxTRANSLATE(
"Stores XFB Copies exclusively on the GPU, bypassing system memory. Causes graphical defects "
"in a small number of games that need to readback from memory.\n\nEnabled = XFB Copies to "
"Texture\nDisabled = XFB Copies to RAM "
"(and Texture)\n\nIf unsure, leave this checked.");
static wxString immediate_xfb_desc =
wxTRANSLATE("Displays the XFB copies as soon as they are created, without waiting for scanout. "
"Can cause graphical defects "
"in some games if the game doesn't expect all XFB copies to be displayed. However, "
"turning this setting on reduces latency."
"\n\nIf unsure, leave this unchecked.");
static wxString stc_desc =
wxTRANSLATE("The \"Safe\" setting eliminates the likelihood of the GPU missing texture updates "
"from RAM.\nLower accuracies cause in-game text to appear garbled in certain "
@@ -229,17 +240,6 @@ static wxString show_netplay_messages_desc =
static wxString texfmt_desc =
wxTRANSLATE("Modify textures to show the format they're encoded in. Needs an emulation reset "
"in most cases.\n\nIf unsure, leave this unchecked.");
static wxString xfb_desc = wxTRANSLATE(
"Disable any XFB emulation.\nSpeeds up emulation a lot but causes heavy glitches in many games "
"which rely on them (especially homebrew applications).\n\nIf unsure, leave this checked.");
static wxString xfb_virtual_desc = wxTRANSLATE(
"Emulate XFBs using GPU texture objects.\nFixes many games which don't work without XFB "
"emulation while not being as slow as real XFB emulation. However, it may still fail for a lot "
"of other games (especially homebrew applications).\n\nIf unsure, leave this checked.");
static wxString xfb_real_desc =
wxTRANSLATE("Emulate XFBs accurately.\nSlows down emulation a lot and prohibits "
"high-resolution rendering but is necessary to emulate a number of games "
"properly.\n\nIf unsure, check virtual XFB emulation instead.");
static wxString dump_textures_desc =
wxTRANSLATE("Dump decoded game textures to User/Dump/Textures/<game_id>/.\n\nIf unsure, leave "
"this unchecked.");
@@ -250,6 +250,8 @@ static wxString cache_hires_textures_desc =
"more RAM but fixes possible stuttering.\n\nIf unsure, leave this unchecked.");
static wxString dump_efb_desc = wxTRANSLATE(
"Dump the contents of EFB copies to User/Dump/Textures/.\n\nIf unsure, leave this unchecked.");
static wxString dump_xfb_desc = wxTRANSLATE(
"Dump the contents of XFB copies to User/Dump/Textures/.\n\nIf unsure, leave this unchecked.");
static wxString internal_resolution_frame_dumping_desc = wxTRANSLATE(
"Create frame dumps and screenshots at the internal resolution of the renderer, rather than "
"the size of the window it is displayed within. If the aspect ratio is widescreen, the output "
@@ -750,20 +752,16 @@ VideoConfigDiag::VideoConfigDiag(wxWindow* parent, const std::string& title)
wxStaticBoxSizer* const group_xfb =
new wxStaticBoxSizer(wxVERTICAL, page_hacks, _("External Frame Buffer (XFB)"));

SettingCheckBox* disable_xfb = CreateCheckBox(
page_hacks, _("Disable"), wxGetTranslation(xfb_desc), Config::GFX_USE_XFB, true);
virtual_xfb = CreateRadioButton(page_hacks, _("Virtual"), wxGetTranslation(xfb_virtual_desc),
Config::GFX_USE_REAL_XFB, true, wxRB_GROUP);
real_xfb = CreateRadioButton(page_hacks, _("Real"), wxGetTranslation(xfb_real_desc),
Config::GFX_USE_REAL_XFB);

wxBoxSizer* const szr = new wxBoxSizer(wxHORIZONTAL);
szr->Add(disable_xfb, 0, wxALIGN_CENTER_VERTICAL);
szr->AddStretchSpacer(1);
szr->Add(virtual_xfb, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, space5);
szr->Add(real_xfb, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, space5);
group_xfb->Add(CreateCheckBox(page_hacks, _("Store XFB Copies to Texture Only"),
wxGetTranslation(skip_xfb_copy_to_ram_desc),
Config::GFX_HACK_SKIP_XFB_COPY_TO_RAM),
0, wxLEFT | wxRIGHT, space5);
group_xfb->AddSpacer(space5);

group_xfb->Add(szr, 1, wxEXPAND | wxLEFT | wxRIGHT, space5);
group_xfb->Add(CreateCheckBox(page_hacks, _("Immediately Present XFB"),
wxGetTranslation(immediate_xfb_desc),
Config::GFX_HACK_IMMEDIATE_XFB),
0, wxLEFT | wxRIGHT, space5);
group_xfb->AddSpacer(space5);

szr_hacks->AddSpacer(space5);
@@ -855,6 +853,9 @@ VideoConfigDiag::VideoConfigDiag(wxWindow* parent, const std::string& title)
szr_utility->Add(CreateCheckBox(page_advanced, _("Dump EFB Target"),
wxGetTranslation(dump_efb_desc),
Config::GFX_DUMP_EFB_TARGET));
szr_utility->Add(CreateCheckBox(page_advanced, _("Dump XFB Target"),
wxGetTranslation(dump_xfb_desc),
Config::GFX_DUMP_XFB_TARGET));
szr_utility->Add(CreateCheckBox(page_advanced, _("Free Look"),
wxGetTranslation(free_look_desc), Config::GFX_FREE_LOOK));
#if defined(HAVE_FFMPEG)
@@ -1053,10 +1054,6 @@ void VideoConfigDiag::OnUpdateUI(wxUpdateUIEvent& ev)
choice_aamode->Enable(vconfig.backend_info.AAModes.size() > 1);
text_aamode->Enable(vconfig.backend_info.AAModes.size() > 1);

// XFB
virtual_xfb->Enable(vconfig.bUseXFB);
real_xfb->Enable(vconfig.bUseXFB);

// custom textures
cache_hires_textures->Enable(vconfig.bHiresTextures);

@@ -38,8 +38,6 @@ set(SRCS
VertexShaderCache.cpp
VertexShaderCache.h
VideoBackend.h
XFBEncoder.cpp
XFBEncoder.h
)

set(LIBS
@@ -52,11 +52,9 @@
<ClCompile Include="PixelShaderCache.cpp" />
<ClCompile Include="PSTextureEncoder.cpp" />
<ClCompile Include="Render.cpp" />
<ClCompile Include="Television.cpp" />
<ClCompile Include="TextureCache.cpp" />
<ClCompile Include="VertexManager.cpp" />
<ClCompile Include="VertexShaderCache.cpp" />
<ClCompile Include="XFBEncoder.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="BoundingBox.h" />
@@ -73,12 +71,10 @@
<ClInclude Include="PixelShaderCache.h" />
<ClInclude Include="PSTextureEncoder.h" />
<ClInclude Include="Render.h" />
<ClInclude Include="Television.h" />
<ClInclude Include="TextureCache.h" />
<ClInclude Include="VertexManager.h" />
<ClInclude Include="VertexShaderCache.h" />
<ClInclude Include="VideoBackend.h" />
<ClInclude Include="XFBEncoder.h" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(CoreDir)VideoCommon\VideoCommon.vcxproj">
@@ -48,9 +48,6 @@
<ClCompile Include="Render.cpp">
<Filter>Render</Filter>
</ClCompile>
<ClCompile Include="Television.cpp">
<Filter>Render</Filter>
</ClCompile>
<ClCompile Include="TextureCache.cpp">
<Filter>Render</Filter>
</ClCompile>
@@ -60,9 +57,6 @@
<ClCompile Include="VertexShaderCache.cpp">
<Filter>Render</Filter>
</ClCompile>
<ClCompile Include="XFBEncoder.cpp">
<Filter>Render</Filter>
</ClCompile>
<ClCompile Include="main.cpp" />
<ClCompile Include="BoundingBox.cpp">
<Filter>Render</Filter>
@@ -108,9 +102,6 @@
<ClInclude Include="Render.h">
<Filter>Render</Filter>
</ClInclude>
<ClInclude Include="Television.h">
<Filter>Render</Filter>
</ClInclude>
<ClInclude Include="TextureCache.h">
<Filter>Render</Filter>
</ClInclude>
@@ -120,9 +111,6 @@
<ClInclude Include="VertexShaderCache.h">
<Filter>Render</Filter>
</ClInclude>
<ClInclude Include="XFBEncoder.h">
<Filter>Render</Filter>
</ClInclude>
<ClInclude Include="VideoBackend.h" />
<ClInclude Include="BoundingBox.h">
<Filter>Render</Filter>
@@ -80,6 +80,7 @@ DXTexture::DXTexture(const TextureConfig& tex_config) : AbstractTexture(tex_conf
DXTexture::~DXTexture()
{
m_texture->Release();
SAFE_RELEASE(m_staging_texture);
}

D3DTexture2D* DXTexture::GetRawTexIdentifier() const
@@ -92,48 +93,72 @@ void DXTexture::Bind(unsigned int stage)
D3D::stateman->SetTexture(stage, m_texture->GetSRV());
}

bool DXTexture::Save(const std::string& filename, unsigned int level)
std::optional<AbstractTexture::RawTextureInfo> DXTexture::MapFullImpl()
{
// We can't dump compressed textures currently (it would mean drawing them to a RGBA8
// framebuffer, and saving that). TextureCache does not call Save for custom textures
// anyway, so this is fine for now.
_assert_(m_config.format == AbstractTextureFormat::RGBA8);

// Create a staging/readback texture with the dimensions of the specified mip level.
u32 mip_width = std::max(m_config.width >> level, 1u);
u32 mip_height = std::max(m_config.height >> level, 1u);
CD3D11_TEXTURE2D_DESC staging_texture_desc(DXGI_FORMAT_R8G8B8A8_UNORM, mip_width, mip_height, 1,
1, 0, D3D11_USAGE_STAGING, D3D11_CPU_ACCESS_READ);

ID3D11Texture2D* staging_texture;
HRESULT hr = D3D::device->CreateTexture2D(&staging_texture_desc, nullptr, &staging_texture);
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 false;
return {};
}

// Copy the selected mip level to the staging texture.
CD3D11_BOX src_box(0, 0, 0, mip_width, mip_height, 1);
D3D::context->CopySubresourceRegion(staging_texture, 0, 0, 0, 0, m_texture->GetTex(),
// 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(staging_texture, 0, D3D11_MAP_READ, 0, &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));
staging_texture->Release();
return false;
return {};
}

bool encode_result =
TextureToPng(reinterpret_cast<u8*>(map.pData), map.RowPitch, filename, mip_width, mip_height);
D3D::context->Unmap(staging_texture, 0);
staging_texture->Release();
return AbstractTexture::RawTextureInfo{reinterpret_cast<u8*>(map.pData), map.RowPitch,
m_config.width, m_config.height};
}

void DXTexture::Unmap()
{
if (!m_staging_texture)
return;

return encode_result;
D3D::context->Unmap(m_staging_texture, 0);
}

void DXTexture::CopyRectangleFromTexture(const AbstractTexture* source,
@@ -19,7 +19,7 @@ class DXTexture final : public AbstractTexture
~DXTexture();

void Bind(unsigned int stage) override;
bool Save(const std::string& filename, unsigned int level) override;
void Unmap() override;

void CopyRectangleFromTexture(const AbstractTexture* source,
const MathUtil::Rectangle<int>& srcrect,
@@ -30,7 +30,12 @@ class DXTexture final : public AbstractTexture
D3DTexture2D* GetRawTexIdentifier() const;

private:
std::optional<RawTextureInfo> MapFullImpl() override;
std::optional<RawTextureInfo> MapRegionImpl(u32 level, u32 x, u32 y, u32 width,
u32 height) override;

D3DTexture2D* m_texture;
ID3D11Texture2D* m_staging_texture = nullptr;
};

} // namespace DX11
@@ -16,12 +16,10 @@
#include "VideoBackends/D3D/PixelShaderCache.h"
#include "VideoBackends/D3D/Render.h"
#include "VideoBackends/D3D/VertexShaderCache.h"
#include "VideoBackends/D3D/XFBEncoder.h"
#include "VideoCommon/VideoConfig.h"

namespace DX11
{
static XFBEncoder s_xfbEncoder;
static bool s_integer_efb_render_target = false;

FramebufferManager::Efb FramebufferManager::m_efb;
@@ -282,15 +280,11 @@ FramebufferManager::FramebufferManager(int target_width, int target_height)
m_efb.resolved_color_tex = nullptr;
m_efb.resolved_depth_tex = nullptr;
}

s_xfbEncoder.Init();
s_integer_efb_render_target = false;
}

FramebufferManager::~FramebufferManager()
{
s_xfbEncoder.Shutdown();

SAFE_RELEASE(m_efb.color_tex);
SAFE_RELEASE(m_efb.color_int_rtv);
SAFE_RELEASE(m_efb.color_temp_tex);
@@ -304,58 +298,4 @@ FramebufferManager::~FramebufferManager()
SAFE_RELEASE(m_efb.resolved_depth_tex);
}

void FramebufferManager::CopyToRealXFB(u32 xfbAddr, u32 fbStride, u32 fbHeight,
const EFBRectangle& sourceRc, float Gamma)
{
u8* dst = Memory::GetPointer(xfbAddr);

// The destination stride can differ from the copy region width, in which case the pixels
// outside the copy region should not be written to.
s_xfbEncoder.Encode(dst, static_cast<u32>(sourceRc.GetWidth()), fbHeight, sourceRc, Gamma);
}

std::unique_ptr<XFBSourceBase> FramebufferManager::CreateXFBSource(unsigned int target_width,
unsigned int target_height,
unsigned int layers)
{
return std::make_unique<XFBSource>(
D3DTexture2D::Create(target_width, target_height,
(D3D11_BIND_FLAG)(D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE),
D3D11_USAGE_DEFAULT, DXGI_FORMAT_R8G8B8A8_UNORM, 1, layers),
layers);
}

std::pair<u32, u32> FramebufferManager::GetTargetSize() const
{
return std::make_pair(m_target_width, m_target_height);
}

void XFBSource::DecodeToTexture(u32 xfbAddr, u32 fbWidth, u32 fbHeight)
{
// DX11's XFB decoder does not use this function.
// YUYV data is decoded in Render::Swap.
}

void XFBSource::CopyEFB(float Gamma)
{
g_renderer->ResetAPIState(); // reset any game specific settings

// Copy EFB data to XFB and restore render target again
const D3D11_VIEWPORT vp = CD3D11_VIEWPORT(0.f, 0.f, (float)texWidth, (float)texHeight);
const D3D11_RECT rect = CD3D11_RECT(0, 0, texWidth, texHeight);

D3D::context->RSSetViewports(1, &vp);
D3D::context->OMSetRenderTargets(1, &tex->GetRTV(), nullptr);
D3D::SetPointCopySampler();

D3D::drawShadedTexQuad(
FramebufferManager::GetEFBColorTexture()->GetSRV(), &rect, g_renderer->GetTargetWidth(),
g_renderer->GetTargetHeight(), PixelShaderCache::GetColorCopyProgram(true),
VertexShaderCache::GetSimpleVertexShader(), VertexShaderCache::GetSimpleInputLayout(),
GeometryShaderCache::GetCopyGeometryShader(), Gamma);

FramebufferManager::BindEFBRenderTarget();
g_renderer->RestoreAPIState();
}

} // namespace DX11
@@ -46,17 +46,6 @@ namespace DX11
// There may be multiple XFBs in GameCube RAM. This is the maximum number to
// virtualize.

struct XFBSource : public XFBSourceBase
{
XFBSource(D3DTexture2D* _tex, int slices) : tex(_tex), m_slices(slices) {}
~XFBSource() { tex->Release(); }
void DecodeToTexture(u32 xfbAddr, u32 fbWidth, u32 fbHeight) override;
void CopyEFB(float Gamma) override;

D3DTexture2D* const tex;
const int m_slices;
};

class FramebufferManager : public FramebufferManagerBase
{
public:
@@ -80,14 +69,6 @@ class FramebufferManager : public FramebufferManagerBase
static void BindEFBRenderTarget(bool bind_depth = true);

private:
std::unique_ptr<XFBSourceBase> CreateXFBSource(unsigned int target_width,
unsigned int target_height,
unsigned int layers) override;
std::pair<u32, u32> GetTargetSize() const override;

void CopyToRealXFB(u32 xfbAddr, u32 fbStride, u32 fbHeight, const EFBRectangle& sourceRc,
float Gamma) override;

static struct Efb
{
D3DTexture2D* color_tex;
@@ -26,6 +26,8 @@ struct EFBEncodeParams
s32 SrcTop;
u32 DestWidth;
u32 ScaleFactor;
float y_scale;
u32 padding[3];
};

PSTextureEncoder::PSTextureEncoder()
@@ -41,8 +43,11 @@ void PSTextureEncoder::Init()
HRESULT hr;

// Create output texture RGBA format
D3D11_TEXTURE2D_DESC t2dd = CD3D11_TEXTURE2D_DESC(DXGI_FORMAT_B8G8R8A8_UNORM, EFB_WIDTH * 4,
EFB_HEIGHT / 4, 1, 1, D3D11_BIND_RENDER_TARGET);
// 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");
@@ -124,14 +129,15 @@ void PSTextureEncoder::Encode(u8* dst, const EFBCopyParams& params, u32 native_w
encode_params.SrcTop = src_rect.top;
encode_params.DestWidth = native_width;
encode_params.ScaleFactor = scale_by_half ? 2 : 1;
encode_params.y_scale = params.y_scale;
D3D::context->UpdateSubresource(m_encodeParams, 0, nullptr, &encode_params, 0, 0);
D3D::stateman->SetPixelConstants(m_encodeParams);

// We also linear filtering for both box filtering and downsampling higher resolutions to 1x
// TODO: This only produces perfect downsampling for 2x IR, other resolutions will need more
// complex down filtering to average all pixels and produce the correct result.
// Also, box filtering won't be correct for anything other than 1x IR
if (scale_by_half || g_renderer->GetEFBScale() != 1)
if (scale_by_half || g_renderer->GetEFBScale() != 1 || params.y_scale > 1.0f)
D3D::SetLinearCopySampler();
else
D3D::SetPointCopySampler();
@@ -24,10 +24,10 @@
#include "VideoBackends/D3D/D3DBase.h"
#include "VideoBackends/D3D/D3DState.h"
#include "VideoBackends/D3D/D3DUtil.h"
#include "VideoBackends/D3D/DXTexture.h"
#include "VideoBackends/D3D/FramebufferManager.h"
#include "VideoBackends/D3D/GeometryShaderCache.h"
#include "VideoBackends/D3D/PixelShaderCache.h"
#include "VideoBackends/D3D/Television.h"
#include "VideoBackends/D3D/TextureCache.h"
#include "VideoBackends/D3D/VertexShaderCache.h"

@@ -39,6 +39,7 @@
#include "VideoCommon/RenderState.h"
#include "VideoCommon/SamplerCommon.h"
#include "VideoCommon/VideoBackendBase.h"
#include "VideoCommon/VideoCommon.h"
#include "VideoCommon/VideoConfig.h"
#include "VideoCommon/XFMemory.h"

@@ -66,11 +67,8 @@ struct GXPipelineState

static u32 s_last_multisamples = 1;
static bool s_last_stereo_mode = false;
static bool s_last_xfb_mode = false;
static bool s_last_fullscreen_mode = false;

static Television s_television;

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;
@@ -85,8 +83,6 @@ static StateCache s_gx_state_cache;

static void SetupDeviceObjects()
{
s_television.Init();

HRESULT hr;

D3D11_DEPTH_STENCIL_DESC ddesc;
@@ -182,36 +178,9 @@ static void TeardownDeviceObjects()
SAFE_RELEASE(s_screenshot_texture);
SAFE_RELEASE(s_3d_vision_texture);

s_television.Shutdown();

s_gx_state_cache.Clear();
}

static void CreateScreenshotTexture()
{
// We can't render anything outside of the backbuffer anyway, so use the backbuffer size as the
// screenshot buffer size.
// This texture is released to be recreated when the window is resized in Renderer::SwapImpl.
D3D11_TEXTURE2D_DESC scrtex_desc = CD3D11_TEXTURE2D_DESC(
DXGI_FORMAT_R8G8B8A8_UNORM, D3D::GetBackBufferWidth(), D3D::GetBackBufferHeight(), 1, 1, 0,
D3D11_USAGE_STAGING, D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE);
HRESULT hr = D3D::device->CreateTexture2D(&scrtex_desc, nullptr, &s_screenshot_texture);
CHECK(hr == S_OK, "Create screenshot staging texture");
D3D::SetDebugObjectName(s_screenshot_texture, "staging screenshot texture");
}

static D3D11_BOX GetScreenshotSourceBox(const TargetRectangle& targetRc)
{
// Since the screenshot buffer is copied back to the CPU via Map(), we can't access pixels that
// fall outside the backbuffer bounds. Therefore, when crop is enabled and the target rect is
// off-screen to the top/left, we clamp the origin at zero, as well as the bottom/right
// coordinates at the backbuffer dimensions. This will result in a rectangle that can be
// smaller than the backbuffer, but never larger.
return CD3D11_BOX(std::max(targetRc.left, 0), std::max(targetRc.top, 0), 0,
std::min(D3D::GetBackBufferWidth(), (unsigned int)targetRc.right),
std::min(D3D::GetBackBufferHeight(), (unsigned int)targetRc.bottom), 1);
}

static void Create3DVisionTexture(int width, int height)
{
// Create a staging texture for 3D vision with signature information in the last row.
@@ -241,7 +210,6 @@ Renderer::Renderer() : ::Renderer(D3D::GetBackBufferWidth(), D3D::GetBackBufferH
{
s_last_multisamples = g_ActiveConfig.iMultisamples;
s_last_stereo_mode = g_ActiveConfig.iStereoMode > 0;
s_last_xfb_mode = g_ActiveConfig.bUseRealXFB;
s_last_fullscreen_mode = D3D::GetFullscreenState();

g_framebuffer_manager = std::make_unique<FramebufferManager>(m_target_width, m_target_height);
@@ -640,24 +608,9 @@ void Renderer::SetBlendingState(const BlendingState& state)
}

// This function has the final picture. We adjust the aspect ratio here.
void Renderer::SwapImpl(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight,
const EFBRectangle& rc, u64 ticks, float Gamma)
void Renderer::SwapImpl(AbstractTexture* texture, const EFBRectangle& xfb_region, u64 ticks,
float Gamma)
{
if ((!m_xfb_written && !g_ActiveConfig.RealXFBEnabled()) || !fbWidth || !fbHeight)
{
Core::Callback_VideoCopiedToXFB(false);
return;
}

u32 xfbCount = 0;
const XFBSourceBase* const* xfbSourceList =
FramebufferManager::GetXFBSource(xfbAddr, fbStride, fbHeight, &xfbCount);
if ((!xfbSourceList || xfbCount == 0) && g_ActiveConfig.bUseXFB && !g_ActiveConfig.bUseRealXFB)
{
Core::Callback_VideoCopiedToXFB(false);
return;
}

ResetAPIState();

// Prepare to copy the XFBs to our backbuffer
@@ -671,90 +624,10 @@ void Renderer::SwapImpl(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight,

// activate linear filtering for the buffer copies
D3D::SetLinearCopySampler();
auto* xfb_texture = static_cast<DXTexture*>(texture);

if (g_ActiveConfig.bUseXFB && g_ActiveConfig.bUseRealXFB)
{
// TODO: Television should be used to render Virtual XFB mode as well.
D3D11_VIEWPORT vp = CD3D11_VIEWPORT((float)targetRc.left, (float)targetRc.top,
(float)targetRc.GetWidth(), (float)targetRc.GetHeight());
D3D::context->RSSetViewports(1, &vp);

s_television.Submit(xfbAddr, fbStride, fbWidth, fbHeight);
s_television.Render();
}
else if (g_ActiveConfig.bUseXFB)
{
// draw each xfb source
for (u32 i = 0; i < xfbCount; ++i)
{
const auto* const xfbSource = static_cast<const XFBSource*>(xfbSourceList[i]);

// use virtual xfb with offset
int xfbHeight = xfbSource->srcHeight;
int xfbWidth = xfbSource->srcWidth;
int hOffset = ((s32)xfbSource->srcAddr - (s32)xfbAddr) / ((s32)fbStride * 2);

TargetRectangle drawRc;
drawRc.top = targetRc.top + hOffset * targetRc.GetHeight() / (s32)fbHeight;
drawRc.bottom = targetRc.top + (hOffset + xfbHeight) * targetRc.GetHeight() / (s32)fbHeight;
drawRc.left = targetRc.left +
(targetRc.GetWidth() - xfbWidth * targetRc.GetWidth() / (s32)fbStride) / 2;
drawRc.right = targetRc.left +
(targetRc.GetWidth() + xfbWidth * targetRc.GetWidth() / (s32)fbStride) / 2;

// The following code disables auto stretch. Kept for reference.
// scale draw area for a 1 to 1 pixel mapping with the draw target
// float vScale = (float)fbHeight / (float)s_backbuffer_height;
// float hScale = (float)fbWidth / (float)s_backbuffer_width;
// drawRc.top *= vScale;
// drawRc.bottom *= vScale;
// drawRc.left *= hScale;
// drawRc.right *= hScale;

TargetRectangle sourceRc;
sourceRc.left = xfbSource->sourceRc.left;
sourceRc.top = xfbSource->sourceRc.top;
sourceRc.right = xfbSource->sourceRc.right;
sourceRc.bottom = xfbSource->sourceRc.bottom;

sourceRc.right -= Renderer::EFBToScaledX(fbStride - fbWidth);

BlitScreen(sourceRc, drawRc, xfbSource->tex, xfbSource->texWidth, xfbSource->texHeight,
Gamma);
}
}
else
{
TargetRectangle sourceRc = Renderer::ConvertEFBRectangle(rc);

// TODO: Improve sampling algorithm for the pixel shader so that we can use the multisampled EFB
// texture as source
D3DTexture2D* read_texture = FramebufferManager::GetResolvedEFBColorTexture();
BlitScreen(sourceRc, targetRc, read_texture, GetTargetWidth(), GetTargetHeight(), Gamma);
}

// Dump frames
if (IsFrameDumping())
{
if (!s_screenshot_texture)
CreateScreenshotTexture();

D3D11_BOX source_box = GetScreenshotSourceBox(targetRc);
unsigned int source_width = source_box.right - source_box.left;
unsigned int source_height = source_box.bottom - source_box.top;
D3D::context->CopySubresourceRegion(s_screenshot_texture, 0, 0, 0, 0,
D3D::GetBackBuffer()->GetTex(), 0, &source_box);

D3D11_MAPPED_SUBRESOURCE map;
D3D::context->Map(s_screenshot_texture, 0, D3D11_MAP_READ, 0, &map);

AVIDump::Frame state = AVIDump::FetchState(ticks);
DumpFrameData(reinterpret_cast<const u8*>(map.pData), source_width, source_height, map.RowPitch,
state);
FinishFrameData();

D3D::context->Unmap(s_screenshot_texture, 0);
}
BlitScreen(xfb_region, targetRc, xfb_texture->GetRawTexIdentifier(),
xfb_texture->GetConfig().width, xfb_texture->GetConfig().height, Gamma);

// Reset viewport for drawing text
D3D11_VIEWPORT vp =
@@ -773,33 +646,20 @@ void Renderer::SwapImpl(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight,
g_texture_cache->OnConfigChanged(g_ActiveConfig);
VertexShaderCache::RetreiveAsyncShaders();

SetWindowSize(fbStride, fbHeight);
SetWindowSize(xfb_texture->GetConfig().width, xfb_texture->GetConfig().height);

const bool window_resized = CheckForResize();
const bool fullscreen = D3D::GetFullscreenState();
const bool fs_changed = s_last_fullscreen_mode != fullscreen;

bool xfbchanged = s_last_xfb_mode != g_ActiveConfig.bUseRealXFB;

if (FramebufferManagerBase::LastXfbWidth() != fbStride ||
FramebufferManagerBase::LastXfbHeight() != fbHeight)
{
xfbchanged = true;
unsigned int xfb_w = (fbStride < 1 || fbStride > MAX_XFB_WIDTH) ? MAX_XFB_WIDTH : fbStride;
unsigned int xfb_h = (fbHeight < 1 || fbHeight > MAX_XFB_HEIGHT) ? MAX_XFB_HEIGHT : fbHeight;
FramebufferManagerBase::SetLastXfbWidth(xfb_w);
FramebufferManagerBase::SetLastXfbHeight(xfb_h);
}

// Flip/present backbuffer to frontbuffer here
D3D::Present();

// Resize the back buffers NOW to avoid flickering
if (CalculateTargetSize() || xfbchanged || window_resized || fs_changed ||
if (CalculateTargetSize() || window_resized || fs_changed ||
s_last_multisamples != g_ActiveConfig.iMultisamples ||
s_last_stereo_mode != (g_ActiveConfig.iStereoMode > 0))
{
s_last_xfb_mode = g_ActiveConfig.bUseRealXFB;
s_last_multisamples = g_ActiveConfig.iMultisamples;
s_last_fullscreen_mode = fullscreen;
PixelShaderCache::InvalidateMSAAShaders();
@@ -46,8 +46,7 @@ class Renderer : public ::Renderer

TargetRectangle ConvertEFBRectangle(const EFBRectangle& rc) override;

void SwapImpl(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const EFBRectangle& rc,
u64 ticks, float Gamma) override;
void SwapImpl(AbstractTexture* texture, const EFBRectangle& rc, u64 ticks, float Gamma) override;

void ClearScreen(const EFBRectangle& rc, bool colorEnable, bool alphaEnable, bool zEnable,
u32 color, u32 z) override;

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

@@ -78,6 +78,8 @@ void VideoBackend::InitBackendInfo()
g_Config.backend_info.bSupportsInternalResolutionFrameDumps = false;
g_Config.backend_info.bSupportsGPUTextureDecoding = false;
g_Config.backend_info.bSupportsST3CTextures = false;
g_Config.backend_info.bSupportsCopyToVram = true;
g_Config.backend_info.bForceCopyToRam = false;
g_Config.backend_info.bSupportsBitfield = false;
g_Config.backend_info.bSupportsDynamicSamplerIndexing = false;
g_Config.backend_info.bSupportsBPTCTextures = false;

This file was deleted.

@@ -43,7 +43,6 @@
<ClCompile Include="VertexManager.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="FramebufferManager.h" />
<ClInclude Include="NullTexture.h" />
<ClInclude Include="PerfQuery.h" />
<ClInclude Include="Render.h" />
@@ -7,14 +7,14 @@
// This backend tries not to do anything in the backend,
// but everything in VideoCommon.

#include "VideoBackends/Null/FramebufferManager.h"
#include "VideoBackends/Null/PerfQuery.h"
#include "VideoBackends/Null/Render.h"
#include "VideoBackends/Null/ShaderCache.h"
#include "VideoBackends/Null/TextureCache.h"
#include "VideoBackends/Null/VertexManager.h"
#include "VideoBackends/Null/VideoBackend.h"

#include "VideoCommon/FramebufferManagerBase.h"
#include "VideoCommon/VideoBackendBase.h"
#include "VideoCommon/VideoCommon.h"
#include "VideoCommon/VideoConfig.h"
@@ -68,7 +68,7 @@ void VideoBackend::Video_Prepare()
g_renderer = std::make_unique<Renderer>();
g_vertex_manager = std::make_unique<VertexManager>();
g_perf_query = std::make_unique<PerfQuery>();
g_framebuffer_manager = std::make_unique<FramebufferManager>();
g_framebuffer_manager = std::make_unique<FramebufferManagerBase>();
g_texture_cache = std::make_unique<TextureCache>();
VertexShaderCache::s_instance = std::make_unique<VertexShaderCache>();
GeometryShaderCache::s_instance = std::make_unique<GeometryShaderCache>();
@@ -36,7 +36,7 @@ TargetRectangle Renderer::ConvertEFBRectangle(const EFBRectangle& rc)
return result;
}

void Renderer::SwapImpl(u32, u32, u32, u32, const EFBRectangle&, u64, float)
void Renderer::SwapImpl(AbstractTexture*, const EFBRectangle&, u64, float)
{
UpdateActiveConfig();
}
@@ -21,8 +21,7 @@ class Renderer : public ::Renderer
void BBoxWrite(int index, u16 value) override {}
TargetRectangle ConvertEFBRectangle(const EFBRectangle& rc) override;

void SwapImpl(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, const EFBRectangle& rc,
u64 ticks, float gamma) override;
void SwapImpl(AbstractTexture* texture, const EFBRectangle& rc, u64 ticks, float Gamma) override;

void ClearScreen(const EFBRectangle& rc, bool colorEnable, bool alphaEnable, bool zEnable,
u32 color, u32 z) override
@@ -33,7 +33,6 @@ bool FramebufferManager::m_enable_stencil_buffer;

GLenum FramebufferManager::m_textureType;
std::vector<GLuint> FramebufferManager::m_efbFramebuffer;
GLuint FramebufferManager::m_xfbFramebuffer;
GLuint FramebufferManager::m_efbColor;
GLuint FramebufferManager::m_efbDepth;
GLuint FramebufferManager::m_efbColorSwap; // for hot swap when reinterpreting EFB pixel formats
@@ -110,7 +109,6 @@ bool FramebufferManager::HasStencilBuffer()
FramebufferManager::FramebufferManager(int targetWidth, int targetHeight, int msaaSamples,
bool enable_stencil_buffer)
{
m_xfbFramebuffer = 0;
m_efbColor = 0;
m_efbDepth = 0;
m_efbColorSwap = 0;
@@ -189,9 +187,6 @@ FramebufferManager::FramebufferManager(int targetWidth, int targetHeight, int ms
CreateTexture(m_textureType, depth_internal_format, depth_pixel_format, depth_data_type);
m_efbColorSwap = CreateTexture(m_textureType, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE);

// Create XFB framebuffer; targets will be created elsewhere.
glGenFramebuffers(1, &m_xfbFramebuffer);

// Bind target textures to EFB framebuffer.
glGenFramebuffers(m_EFBLayers, m_efbFramebuffer.data());
BindLayeredTexture(m_efbColor, m_efbFramebuffer, GL_COLOR_ATTACHMENT0, m_textureType);
@@ -419,9 +414,6 @@ FramebufferManager::~FramebufferManager()
m_efbFramebuffer.clear();
m_resolvedFramebuffer.clear();

glDeleteFramebuffers(1, &m_xfbFramebuffer);
m_xfbFramebuffer = 0;

glObj[0] = m_resolvedColorTexture;
glObj[1] = m_resolvedDepthTexture;
glDeleteTextures(2, glObj);
@@ -527,21 +519,6 @@ void FramebufferManager::ResolveEFBStencilTexture()
glBindFramebuffer(GL_FRAMEBUFFER, m_efbFramebuffer[0]);
}

void FramebufferManager::CopyToRealXFB(u32 xfbAddr, u32 fbStride, u32 fbHeight,
const EFBRectangle& sourceRc, float Gamma)
{
u8* xfb_in_ram = Memory::GetPointer(xfbAddr);
if (!xfb_in_ram)
{
WARN_LOG(VIDEO, "Tried to copy to invalid XFB address");
return;
}

TargetRectangle targetRc = g_renderer->ConvertEFBRectangle(sourceRc);
TextureConverter::EncodeToRamYUYV(ResolveAndGetRenderTarget(sourceRc), targetRc, xfb_in_ram,
sourceRc.GetWidth(), fbStride, fbHeight);
}

GLuint FramebufferManager::GetResolvedFramebuffer()
{
if (m_msaaSamples <= 1)
@@ -610,61 +587,6 @@ void FramebufferManager::ReinterpretPixelData(unsigned int convtype)
g_renderer->RestoreAPIState();
}

XFBSource::~XFBSource()
{
glDeleteTextures(1, &texture);
}

void XFBSource::DecodeToTexture(u32 xfbAddr, u32 fbWidth, u32 fbHeight)
{
TextureConverter::DecodeToTexture(xfbAddr, fbWidth, fbHeight, texture);
}

void XFBSource::CopyEFB(float Gamma)
{
g_renderer->ResetAPIState();

// Copy EFB data to XFB and restore render target again
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, FramebufferManager::GetXFBFramebuffer());

for (int i = 0; i < m_layers; i++)
{
// Bind EFB and texture layer
glBindFramebuffer(GL_READ_FRAMEBUFFER, FramebufferManager::GetEFBFramebuffer(i));
glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0, i);

glBlitFramebuffer(0, 0, texWidth, texHeight, 0, 0, texWidth, texHeight, GL_COLOR_BUFFER_BIT,
GL_NEAREST);
}

// Return to EFB.
FramebufferManager::SetFramebuffer(0);

g_renderer->RestoreAPIState();
}

std::unique_ptr<XFBSourceBase> FramebufferManager::CreateXFBSource(unsigned int target_width,
unsigned int target_height,
unsigned int layers)
{
GLuint texture;

glGenTextures(1, &texture);

glActiveTexture(GL_TEXTURE9);
glBindTexture(GL_TEXTURE_2D_ARRAY, texture);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, 0);
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, target_width, target_height, layers, 0, GL_RGBA,
GL_UNSIGNED_BYTE, nullptr);

return std::make_unique<XFBSource>(texture, layers);
}

std::pair<u32, u32> FramebufferManager::GetTargetSize() const
{
return std::make_pair(m_targetWidth, m_targetHeight);
}

void FramebufferManager::PokeEFB(EFBAccessType type, const EfbPokeData* points, size_t num_points)
{
g_renderer->ResetAPIState();
@@ -48,18 +48,6 @@

namespace OGL
{
struct XFBSource : public XFBSourceBase
{
XFBSource(GLuint tex, int layers) : texture(tex), m_layers(layers) {}
~XFBSource();

void CopyEFB(float Gamma) override;
void DecodeToTexture(u32 xfbAddr, u32 fbWidth, u32 fbHeight) override;

const GLuint texture;
const int m_layers;
};

class FramebufferManager : public FramebufferManagerBase
{
public:
@@ -77,7 +65,6 @@ class FramebufferManager : public FramebufferManagerBase
{
return (layer < m_EFBLayers) ? m_efbFramebuffer[layer] : m_efbFramebuffer.back();
}
static GLuint GetXFBFramebuffer() { return m_xfbFramebuffer; }
// Resolved framebuffer is only used in MSAA mode.
static GLuint GetResolvedFramebuffer();
static void SetFramebuffer(GLuint fb);
@@ -109,21 +96,13 @@ class FramebufferManager : public FramebufferManagerBase
GLenum data_type);
void BindLayeredTexture(GLuint texture, const std::vector<GLuint>& framebuffers,
GLenum attachment, GLenum texture_type);
std::unique_ptr<XFBSourceBase> CreateXFBSource(unsigned int target_width,
unsigned int target_height,
unsigned int layers) override;
std::pair<u32, u32> GetTargetSize() const override;

void CopyToRealXFB(u32 xfbAddr, u32 fbStride, u32 fbHeight, const EFBRectangle& sourceRc,
float Gamma) override;

static int m_targetWidth;
static int m_targetHeight;
static int m_msaaSamples;

static GLenum m_textureType;
static std::vector<GLuint> m_efbFramebuffer;
static GLuint m_xfbFramebuffer;
static GLuint m_efbColor;
static GLuint m_efbDepth;
static GLuint
@@ -66,22 +66,6 @@ GLenum GetGLTypeForTextureFormat(AbstractTextureFormat format)
}
} // Anonymous namespace

bool SaveTexture(const std::string& filename, u32 textarget, u32 tex, int virtual_width,
int virtual_height, unsigned int level)
{
if (GLInterface->GetMode() != GLInterfaceMode::MODE_OPENGL)
return false;
int width = std::max(virtual_width >> level, 1);
int height = std::max(virtual_height >> level, 1);
std::vector<u8> data(width * height * 4);
glActiveTexture(GL_TEXTURE9);
glBindTexture(textarget, tex);
glGetTexImage(textarget, level, GL_RGBA, GL_UNSIGNED_BYTE, data.data());
OGLTexture::SetStage();

return TextureToPng(data.data(), width * 4, filename, width, height, true);
}

OGLTexture::OGLTexture(const TextureConfig& tex_config) : AbstractTexture(tex_config)
{
glGenTextures(1, &m_texId);
@@ -164,15 +148,73 @@ void OGLTexture::Bind(unsigned int stage)
}
}

bool OGLTexture::Save(const std::string& filename, unsigned int level)
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)
{
// We can't dump compressed textures currently (it would mean drawing them to a RGBA8
// framebuffer, and saving that). TextureCache does not call Save for custom textures
// anyway, so this is fine for now.
_assert_(m_config.format == AbstractTextureFormat::RGBA8);
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());
}
else
{
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);

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;

std::vector<u8> partial_data;
partial_data.resize(partial_stride * height);

const u32 staging_stride = m_config.width * 4;
const u32 x_offset = x * 4;

auto staging_location = m_staging_data.begin() + staging_stride * y;
auto partial_location = partial_data.begin();

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;
}

return SaveTexture(filename, GL_TEXTURE_2D_ARRAY, m_texId, m_config.width, m_config.height,
level);
// Now swap the region back in for the staging data
m_staging_data.swap(partial_data);
}

void OGLTexture::CopyRectangleFromTexture(const AbstractTexture* source,
@@ -4,6 +4,8 @@

#pragma once

#include <vector>

#include "Common/GL/GLUtil.h"

#include "VideoCommon/AbstractTexture.h"
@@ -17,7 +19,6 @@ class OGLTexture final : public AbstractTexture
~OGLTexture();

void Bind(unsigned int stage) override;
bool Save(const std::string& filename, unsigned int level) override;

void CopyRectangleFromTexture(const AbstractTexture* source,
const MathUtil::Rectangle<int>& srcrect,
@@ -32,8 +33,14 @@ class OGLTexture final : public AbstractTexture
static void SetStage();

private:
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);

GLuint m_texId;
GLuint m_framebuffer = 0;
std::vector<u8> m_staging_data;
};

} // namespace OGL
@@ -25,7 +25,7 @@ static const char s_vertex_shader[] = "out vec2 uv0;\n"
"void main(void) {\n"
" vec2 rawpos = vec2(gl_VertexID&1, gl_VertexID&2);\n"
" gl_Position = vec4(rawpos*2.0-1.0, 0.0, 1.0);\n"
" uv0 = rawpos * src_rect.zw + src_rect.xy;\n"
" uv0 = vec2(mix(src_rect.xy, src_rect.zw, rawpos));\n"
"}\n";

OpenGLPostProcessing::OpenGLPostProcessing() : m_initialized(false)
@@ -52,8 +52,8 @@ void OpenGLPostProcessing::BlitFromTexture(TargetRectangle src, TargetRectangle

glUniform4f(m_uniform_resolution, (float)src_width, (float)src_height, 1.0f / (float)src_width,
1.0f / (float)src_height);
glUniform4f(m_uniform_src_rect, src.left / (float)src_width, src.bottom / (float)src_height,
src.GetWidth() / (float)src_width, src.GetHeight() / (float)src_height);
glUniform4f(m_uniform_src_rect, src.left / (float)src_width, src.top / (float)src_height,
src.right / (float)src_width, src.bottom / (float)src_height);
glUniform1ui(m_uniform_time, (GLuint)m_timer.GetTimeElapsed());
glUniform1i(m_uniform_layer, layer);

@@ -66,7 +66,6 @@ static std::unique_ptr<RasterFont> s_raster_font;
static int s_MSAASamples = 1;
static u32 s_last_multisamples = 1;
static bool s_last_stereo_mode = false;
static bool s_last_xfb_mode = false;

static bool s_vsync;

@@ -461,6 +460,7 @@ Renderer::Renderer()
GLExtensions::Supports("GL_EXT_copy_image") ||
GLExtensions::Supports("GL_OES_copy_image")) &&
!DriverDetails::HasBug(DriverDetails::BUG_BROKEN_COPYIMAGE);
g_ogl_config.bSupportTextureSubImage = GLExtensions::Supports("ARB_get_texture_sub_image");

// Desktop OpenGL supports the binding layout if it supports 420pack
// OpenGL ES 3.1 supports it implicitly without an extension
@@ -726,7 +726,6 @@ Renderer::Renderer()
s_MSAASamples = s_last_multisamples;

s_last_stereo_mode = g_ActiveConfig.iStereoMode > 0;
s_last_xfb_mode = g_ActiveConfig.bUseRealXFB;

// Handle VSync on/off
s_vsync = g_ActiveConfig.IsVSync();
@@ -791,12 +790,7 @@ Renderer::Renderer()
ClearEFBCache();
}

Renderer::~Renderer()
{
FlushFrameDump();
FinishFrameData();
DestroyFrameDumpResources();
}
Renderer::~Renderer() = default;

void Renderer::Shutdown()
{
@@ -1331,8 +1325,8 @@ void Renderer::SetBlendingState(const BlendingState& state)
}

// This function has the final picture. We adjust the aspect ratio here.
void Renderer::SwapImpl(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight,
const EFBRectangle& rc, u64 ticks, float Gamma)
void Renderer::SwapImpl(AbstractTexture* texture, const EFBRectangle& xfb_region, u64 ticks,
float Gamma)
{
if (g_ogl_config.bSupportsDebug)
{
@@ -1342,20 +1336,11 @@ void Renderer::SwapImpl(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight,
glDisable(GL_DEBUG_OUTPUT);
}

if ((!m_xfb_written && !g_ActiveConfig.RealXFBEnabled()) || !fbWidth || !fbHeight)
{
Core::Callback_VideoCopiedToXFB(false);
return;
}
auto* xfb_texture = static_cast<OGLTexture*>(texture);

u32 xfbCount = 0;
const XFBSourceBase* const* xfbSourceList =
FramebufferManager::GetXFBSource(xfbAddr, fbStride, fbHeight, &xfbCount);
if (g_ActiveConfig.VirtualXFBEnabled() && (!xfbSourceList || xfbCount == 0))
{
Core::Callback_VideoCopiedToXFB(false);
return;
}
TargetRectangle sourceRc = xfb_region;
sourceRc.top = xfb_region.GetHeight();
sourceRc.bottom = 0;

ResetAPIState();

@@ -1366,49 +1351,16 @@ void Renderer::SwapImpl(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight,
std::swap(flipped_trc.top, flipped_trc.bottom);

// Copy the framebuffer to screen.
DrawFrame(0, flipped_trc, rc, xfbAddr, xfbSourceList, xfbCount, fbWidth, fbStride, fbHeight);

// The FlushFrameDump call here is necessary even after frame dumping is stopped.
// If left out, screenshots are "one frame" behind, as an extra frame is dumped and buffered.
FlushFrameDump();
if (IsFrameDumping())
{
// Currently, we only use the off-screen buffer as a frame dump source if full-resolution
// frame dumping is enabled, saving the need for an extra copy. In the future, this could
// be extended to be used for surfaceless contexts as well.
bool use_offscreen_buffer = g_ActiveConfig.bInternalResolutionFrameDumps;
if (use_offscreen_buffer)
{
// DumpFrameUsingFBO resets GL_FRAMEBUFFER, so change back to the window for drawing OSD.
DumpFrameUsingFBO(rc, xfbAddr, xfbSourceList, xfbCount, fbWidth, fbStride, fbHeight, ticks);
}
else
{
// GL_READ_FRAMEBUFFER is set by GL_FRAMEBUFFER in DrawFrame -> Draw{EFB,VirtualXFB,RealXFB}.
DumpFrame(flipped_trc, ticks);
}
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
BlitScreen(sourceRc, flipped_trc, xfb_texture->GetRawTexIdentifier(),
xfb_texture->GetConfig().width, xfb_texture->GetConfig().height);

// Finish up the current frame, print some stats

SetWindowSize(fbStride, fbHeight);
SetWindowSize(xfb_texture->GetConfig().width, xfb_texture->GetConfig().height);

GLInterface->Update(); // just updates the render window position and the backbuffer size

bool xfbchanged = s_last_xfb_mode != g_ActiveConfig.bUseRealXFB;

if (FramebufferManagerBase::LastXfbWidth() != fbStride ||
FramebufferManagerBase::LastXfbHeight() != fbHeight)
{
xfbchanged = true;
unsigned int const last_w =
(fbStride < 1 || fbStride > MAX_XFB_WIDTH) ? MAX_XFB_WIDTH : fbStride;
unsigned int const last_h =
(fbHeight < 1 || fbHeight > MAX_XFB_HEIGHT) ? MAX_XFB_HEIGHT : fbHeight;
FramebufferManagerBase::SetLastXfbWidth(last_w);
FramebufferManagerBase::SetLastXfbHeight(last_h);
}

bool window_resized = false;
int window_width = static_cast<int>(std::max(GLInterface->GetBackBufferWidth(), 1u));
int window_height = static_cast<int>(std::max(GLInterface->GetBackBufferHeight(), 1u));
@@ -1428,9 +1380,8 @@ void Renderer::SwapImpl(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight,
stencil_buffer_enabled != BoundingBox::NeedsStencilBuffer() ||
s_last_stereo_mode != (g_ActiveConfig.iStereoMode > 0);

if (xfbchanged || window_resized || fb_needs_update)
if (window_resized || fb_needs_update)
{
s_last_xfb_mode = g_ActiveConfig.bUseRealXFB;
UpdateDrawRectangle();
}
if (fb_needs_update)
@@ -1523,241 +1474,13 @@ void Renderer::SwapImpl(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight,
ClearEFBCache();
}

void Renderer::DrawFrame(GLuint framebuffer, const TargetRectangle& target_rc,
const EFBRectangle& source_rc, u32 xfb_addr,
const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width,
u32 fb_stride, u32 fb_height)
{
if (g_ActiveConfig.bUseXFB)
{
if (g_ActiveConfig.bUseRealXFB)
DrawRealXFB(framebuffer, target_rc, xfb_sources, xfb_count, fb_width, fb_stride, fb_height);
else
DrawVirtualXFB(framebuffer, target_rc, xfb_addr, xfb_sources, xfb_count, fb_width, fb_stride,
fb_height);
}
else
{
DrawEFB(framebuffer, target_rc, source_rc);
}
}

void Renderer::DrawEFB(GLuint framebuffer, const TargetRectangle& target_rc,
const EFBRectangle& source_rc)
const TargetRectangle& source_rc)
{
TargetRectangle scaled_source_rc = ConvertEFBRectangle(source_rc);

// for msaa mode, we must resolve the efb content to non-msaa
GLuint tex = FramebufferManager::ResolveAndGetRenderTarget(source_rc);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
BlitScreen(scaled_source_rc, target_rc, tex, m_target_width, m_target_height);
}

void Renderer::DrawVirtualXFB(GLuint framebuffer, const TargetRectangle& target_rc, u32 xfb_addr,
const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width,
u32 fb_stride, u32 fb_height)
{
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);

for (u32 i = 0; i < xfb_count; ++i)
{
const XFBSource* xfbSource = static_cast<const XFBSource*>(xfb_sources[i]);

TargetRectangle draw_rc;
TargetRectangle source_rc;
source_rc.left = xfbSource->sourceRc.left;
source_rc.right = xfbSource->sourceRc.right;
source_rc.top = xfbSource->sourceRc.top;
source_rc.bottom = xfbSource->sourceRc.bottom;

// use virtual xfb with offset
int xfbHeight = xfbSource->srcHeight;
int xfbWidth = xfbSource->srcWidth;
int hOffset = (static_cast<s32>(xfbSource->srcAddr) - static_cast<s32>(xfb_addr)) /
(static_cast<s32>(fb_stride) * 2);

draw_rc.top = target_rc.top - hOffset * target_rc.GetHeight() / static_cast<s32>(fb_height);
draw_rc.bottom =
target_rc.top - (hOffset + xfbHeight) * target_rc.GetHeight() / static_cast<s32>(fb_height);
draw_rc.left =
target_rc.left +
(target_rc.GetWidth() - xfbWidth * target_rc.GetWidth() / static_cast<s32>(fb_stride)) / 2;
draw_rc.right =
target_rc.left +
(target_rc.GetWidth() + xfbWidth * target_rc.GetWidth() / static_cast<s32>(fb_stride)) / 2;

// The following code disables auto stretch. Kept for reference.
// scale draw area for a 1 to 1 pixel mapping with the draw target
// float h_scale = static_cast<float>(fb_width) / static_cast<float>(target_rc.GetWidth());
// float v_scale = static_cast<float>(fb_height) / static_cast<float>(target_rc.GetHeight());
// draw_rc.top *= v_scale;
// draw_rc.bottom *= v_scale;
// draw_rc.left *= h_scale;
// draw_rc.right *= h_scale;

source_rc.right -= Renderer::EFBToScaledX(fb_stride - fb_width);

BlitScreen(source_rc, draw_rc, xfbSource->texture, xfbSource->texWidth, xfbSource->texHeight);
}
}

void Renderer::DrawRealXFB(GLuint framebuffer, const TargetRectangle& target_rc,
const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width,
u32 fb_stride, u32 fb_height)
{
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);

for (u32 i = 0; i < xfb_count; ++i)
{
const XFBSource* xfbSource = static_cast<const XFBSource*>(xfb_sources[i]);

TargetRectangle source_rc;
source_rc.left = xfbSource->sourceRc.left;
source_rc.right = xfbSource->sourceRc.right;
source_rc.top = xfbSource->sourceRc.top;
source_rc.bottom = xfbSource->sourceRc.bottom;

source_rc.right -= fb_stride - fb_width;

// RealXFB doesn't call ConvertEFBRectangle for sourceRc, therefore it is still assuming a top-
// left origin. The top offset is always zero (see FramebufferManagerBase::GetRealXFBSource).
source_rc.top = source_rc.bottom;
source_rc.bottom = 0;

TargetRectangle draw_rc = target_rc;
BlitScreen(source_rc, draw_rc, xfbSource->texture, xfbSource->texWidth, xfbSource->texHeight);
}
}

void Renderer::FlushFrameDump()
{
if (!m_last_frame_exported)
return;

FinishFrameData();
glBindBuffer(GL_PIXEL_PACK_BUFFER, m_frame_dumping_pbo[0]);
m_frame_pbo_is_mapped[0] = true;
void* data = glMapBufferRange(
GL_PIXEL_PACK_BUFFER, 0, m_last_frame_width[0] * m_last_frame_height[0] * 4, GL_MAP_READ_BIT);
DumpFrameData(reinterpret_cast<u8*>(data), m_last_frame_width[0], m_last_frame_height[0],
m_last_frame_width[0] * 4, m_last_frame_state, true);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
m_last_frame_exported = false;
}

void Renderer::DumpFrame(const TargetRectangle& flipped_trc, u64 ticks)
{
if (!m_frame_dumping_pbo[0])
{
glGenBuffers(2, m_frame_dumping_pbo.data());
glBindBuffer(GL_PIXEL_PACK_BUFFER, m_frame_dumping_pbo[0]);
}
else
{
FlushFrameDump();
std::swap(m_frame_dumping_pbo[0], m_frame_dumping_pbo[1]);
std::swap(m_frame_pbo_is_mapped[0], m_frame_pbo_is_mapped[1]);
std::swap(m_last_frame_width[0], m_last_frame_width[1]);
std::swap(m_last_frame_height[0], m_last_frame_height[1]);
glBindBuffer(GL_PIXEL_PACK_BUFFER, m_frame_dumping_pbo[0]);
if (m_frame_pbo_is_mapped[0])
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
m_frame_pbo_is_mapped[0] = false;
}

if (flipped_trc.GetWidth() != m_last_frame_width[0] ||
flipped_trc.GetHeight() != m_last_frame_height[0])
{
m_last_frame_width[0] = flipped_trc.GetWidth();
m_last_frame_height[0] = flipped_trc.GetHeight();
glBufferData(GL_PIXEL_PACK_BUFFER, m_last_frame_width[0] * m_last_frame_height[0] * 4, nullptr,
GL_STREAM_READ);
}

m_last_frame_state = AVIDump::FetchState(ticks);
m_last_frame_exported = true;

glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadPixels(flipped_trc.left, flipped_trc.bottom, m_last_frame_width[0], m_last_frame_height[0],
GL_RGBA, GL_UNSIGNED_BYTE, 0);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
}

void Renderer::DumpFrameUsingFBO(const EFBRectangle& source_rc, u32 xfb_addr,
const XFBSourceBase* const* xfb_sources, u32 xfb_count,
u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks)
{
// This needs to be converted to the GL bottom-up window coordinate system.
TargetRectangle render_rc = CalculateFrameDumpDrawRectangle();
std::swap(render_rc.top, render_rc.bottom);

// Ensure the render texture meets the size requirements of the draw area.
u32 render_width = static_cast<u32>(render_rc.GetWidth());
u32 render_height = static_cast<u32>(render_rc.GetHeight());
PrepareFrameDumpRenderTexture(render_width, render_height);

// Ensure the alpha channel of the render texture is blank. The frame dump backend expects
// that the alpha is set to 1.0 for all pixels.
glBindFramebuffer(GL_FRAMEBUFFER, m_frame_dump_render_framebuffer);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);

// Render the frame into the frame dump render texture. Disable alpha writes in case the
// post-processing shader writes a non-1.0 value.
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
DrawFrame(m_frame_dump_render_framebuffer, render_rc, source_rc, xfb_addr, xfb_sources, xfb_count,
fb_width, fb_stride, fb_height);

// Copy frame to output buffer. This assumes that GL_FRAMEBUFFER has been set.
DumpFrame(render_rc, ticks);

// Restore state after drawing. This isn't the game state, it's the state set by ResetAPIState.
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

void Renderer::PrepareFrameDumpRenderTexture(u32 width, u32 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 != 0 && m_frame_dump_render_framebuffer != 0 &&
m_frame_dump_render_texture_width >= width && m_frame_dump_render_texture_height >= height)
{
return;
}

// Recreate texture objects.
if (m_frame_dump_render_texture != 0)
glDeleteTextures(1, &m_frame_dump_render_texture);
if (m_frame_dump_render_framebuffer != 0)
glDeleteFramebuffers(1, &m_frame_dump_render_framebuffer);

glGenTextures(1, &m_frame_dump_render_texture);
glActiveTexture(GL_TEXTURE9);
glBindTexture(GL_TEXTURE_2D, m_frame_dump_render_texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

glGenFramebuffers(1, &m_frame_dump_render_framebuffer);
FramebufferManager::SetFramebuffer(m_frame_dump_render_framebuffer);
FramebufferManager::FramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
m_frame_dump_render_texture, 0);

m_frame_dump_render_texture_width = width;
m_frame_dump_render_texture_height = height;
OGLTexture::SetStage();
}

void Renderer::DestroyFrameDumpResources()
{
if (m_frame_dump_render_framebuffer)
glDeleteFramebuffers(1, &m_frame_dump_render_framebuffer);
if (m_frame_dump_render_texture)
glDeleteTextures(1, &m_frame_dump_render_texture);
if (m_frame_dumping_pbo[0])
glDeleteBuffers(2, m_frame_dumping_pbo.data());
BlitScreen(source_rc, target_rc, tex, m_target_width, m_target_height);
}

// ALWAYS call RestoreAPIState for each ResetAPIState call you're doing
@@ -4,7 +4,6 @@

#pragma once

#include <array>
#include <string>

#include "Common/GL/GLUtil.h"
@@ -59,6 +58,7 @@ struct VideoConfig
bool bSupportsImageLoadStore;
bool bSupportsAniso;
bool bSupportsBitfield;
bool bSupportTextureSubImage;

const char* gl_vendor;
const char* gl_renderer;
@@ -98,8 +98,7 @@ class Renderer : public ::Renderer

TargetRectangle ConvertEFBRectangle(const EFBRectangle& rc) override;

void SwapImpl(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const EFBRectangle& rc,
u64 ticks, float Gamma) override;
void SwapImpl(AbstractTexture* texture, const EFBRectangle& rc, u64 ticks, float Gamma) override;

void ClearScreen(const EFBRectangle& rc, bool colorEnable, bool alphaEnable, bool zEnable,
u32 color, u32 z) override;
@@ -112,42 +111,10 @@ class Renderer : public ::Renderer
void UpdateEFBCache(EFBAccessType type, u32 cacheRectIdx, const EFBRectangle& efbPixelRc,
const TargetRectangle& targetPixelRc, const void* data);

// Draw either the EFB, or specified XFB sources to the currently-bound framebuffer.
void DrawFrame(GLuint framebuffer, const TargetRectangle& target_rc,
const EFBRectangle& source_rc, u32 xfb_addr,
const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width,
u32 fb_stride, u32 fb_height);
void DrawEFB(GLuint framebuffer, const TargetRectangle& target_rc, const EFBRectangle& source_rc);
void DrawVirtualXFB(GLuint framebuffer, const TargetRectangle& target_rc, u32 xfb_addr,
const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width,
u32 fb_stride, u32 fb_height);
void DrawRealXFB(GLuint framebuffer, const TargetRectangle& target_rc,
const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width,
u32 fb_stride, u32 fb_height);
void DrawEFB(GLuint framebuffer, const TargetRectangle& target_rc,
const TargetRectangle& source_rc);

void BlitScreen(TargetRectangle src, TargetRectangle dst, GLuint src_texture, int src_width,
int src_height);

void FlushFrameDump();
void DumpFrame(const TargetRectangle& flipped_trc, u64 ticks);
void DumpFrameUsingFBO(const EFBRectangle& source_rc, u32 xfb_addr,
const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width,
u32 fb_stride, u32 fb_height, u64 ticks);

// Frame dumping framebuffer, we render to this, then read it back
void PrepareFrameDumpRenderTexture(u32 width, u32 height);
void DestroyFrameDumpResources();
GLuint m_frame_dump_render_texture = 0;
GLuint m_frame_dump_render_framebuffer = 0;
u32 m_frame_dump_render_texture_width = 0;
u32 m_frame_dump_render_texture_height = 0;

// avi dumping state to delay one frame
std::array<u32, 2> m_frame_dumping_pbo = {};
std::array<bool, 2> m_frame_pbo_is_mapped = {};
std::array<int, 2> m_last_frame_width = {};
std::array<int, 2> m_last_frame_height = {};
bool m_last_frame_exported = false;
AVIDump::Frame m_last_frame_state;
};
}
@@ -388,9 +388,10 @@ void main()
void TextureCache::CreateTextureDecodingResources()
{
static const GLenum gl_view_types[TextureConversionShader::BUFFER_FORMAT_COUNT] = {
GL_R8UI, // BUFFER_FORMAT_R8_UINT
GL_R16UI, // BUFFER_FORMAT_R16_UINT
GL_RG32UI, // BUFFER_FORMAT_R32G32_UINT
GL_R8UI, // BUFFER_FORMAT_R8_UINT
GL_R16UI, // BUFFER_FORMAT_R16_UINT
GL_RG32UI, // BUFFER_FORMAT_R32G32_UINT
GL_RGBA8UI, // BUFFER_FORMAT_RGBA8_UINT
};

glGenTextures(TextureConversionShader::BUFFER_FORMAT_COUNT,
@@ -41,101 +41,16 @@ static GLuint s_dstTexture = 0; // for encoding to RAM
const int renderBufferWidth = EFB_WIDTH * 4;
const int renderBufferHeight = 1024;

static SHADER s_rgbToYuyvProgram;
static int s_rgbToYuyvUniform_loc;

static SHADER s_yuyvToRgbProgram;

struct EncodingProgram
{
SHADER program;
GLint copy_position_uniform;
GLint y_scale_uniform;
};
static std::map<EFBCopyParams, EncodingProgram> s_encoding_programs;

static GLuint s_PBO = 0; // for readback with different strides

static void CreatePrograms()
{
/* TODO: Accuracy Improvements
*
* This shader doesn't really match what the GameCube does internally in the
* copy pipeline.
* 1. It uses OpenGL's built in filtering when yscaling, someone could work
* out how the copypipeline does it's filtering and implement it correctly
* in this shader.
* 2. Deflickering isn't implemented, a futher filtering over 3 lines.
* Isn't really needed on non-interlaced monitors (and would lower quality;
* But hey, accuracy!)
* 3. Flipper's YUYV conversion implements a 3 pixel horizontal blur on the
* UV channels, centering the U channel on the Left pixel and the V channel
* on the Right pixel.
* The current implementation Centers both UV channels at the same place
* inbetween the two Pixels, and only blurs over these two pixels.
*/
// Output is BGRA because that is slightly faster than RGBA.
const char* VProgramRgbToYuyv =
"out vec2 uv0;\n"
"uniform vec4 copy_position;\n" // left, top, right, bottom
"SAMPLER_BINDING(9) uniform sampler2DArray samp9;\n"
"void main()\n"
"{\n"
" vec2 rawpos = vec2(gl_VertexID&1, gl_VertexID&2);\n"
" gl_Position = vec4(rawpos*2.0-1.0, 0.0, 1.0);\n"
" uv0 = mix(copy_position.xy, copy_position.zw, rawpos) / vec2(textureSize(samp9, 0).xy);\n"
"}\n";
const char* FProgramRgbToYuyv =
"SAMPLER_BINDING(9) uniform sampler2DArray samp9;\n"
"in vec2 uv0;\n"
"out vec4 ocol0;\n"
"void main()\n"
"{\n"
" vec3 c0 = texture(samp9, vec3(uv0 - dFdx(uv0) * 0.25, 0.0)).rgb;\n"
" vec3 c1 = texture(samp9, vec3(uv0 + dFdx(uv0) * 0.25, 0.0)).rgb;\n"
" vec3 c01 = (c0 + c1) * 0.5;\n"
" vec3 y_const = vec3(0.257,0.504,0.098);\n"
" vec3 u_const = vec3(-0.148,-0.291,0.439);\n"
" vec3 v_const = vec3(0.439,-0.368,-0.071);\n"
" vec4 const3 = vec4(0.0625,0.5,0.0625,0.5);\n"
" ocol0 = vec4(dot(c1,y_const),dot(c01,u_const),dot(c0,y_const),dot(c01, v_const)) + "
"const3;\n"
"}\n";
ProgramShaderCache::CompileShader(s_rgbToYuyvProgram, VProgramRgbToYuyv, FProgramRgbToYuyv);
s_rgbToYuyvUniform_loc = glGetUniformLocation(s_rgbToYuyvProgram.glprogid, "copy_position");

/* TODO: Accuracy Improvements
*
* The YVYU to RGB conversion here matches the RGB to YUYV done above, but
* if a game modifies or adds images to the XFB then it should be using the
* same algorithm as the flipper, and could result in slight color inaccuracies
* when run back through this shader.
*/
const char* VProgramYuyvToRgb = "void main()\n"
"{\n"
" vec2 rawpos = vec2(gl_VertexID&1, gl_VertexID&2);\n"
" gl_Position = vec4(rawpos*2.0-1.0, 0.0, 1.0);\n"
"}\n";
const char* FProgramYuyvToRgb = "SAMPLER_BINDING(9) uniform sampler2D samp9;\n"
"in vec2 uv0;\n"
"out vec4 ocol0;\n"
"void main()\n"
"{\n"
" ivec2 uv = ivec2(gl_FragCoord.xy);\n"
// We switch top/bottom here. TODO: move this to screen blit.
" ivec2 ts = textureSize(samp9, 0);\n"
" vec4 c0 = texelFetch(samp9, ivec2(uv.x>>1, ts.y-uv.y-1), 0);\n"
" float y = mix(c0.r, c0.b, (uv.x & 1) == 1);\n"
" float yComp = 1.164 * (y - 0.0625);\n"
" float uComp = c0.g - 0.5;\n"
" float vComp = c0.a - 0.5;\n"
" ocol0 = vec4(yComp + (1.596 * vComp),\n"
" yComp - (0.813 * vComp) - (0.391 * uComp),\n"
" yComp + (2.018 * uComp),\n"
" 1.0);\n"
"}\n";
ProgramShaderCache::CompileShader(s_yuyvToRgbProgram, VProgramYuyvToRgb, FProgramYuyvToRgb);
}

static EncodingProgram& GetOrCreateEncodingShader(const EFBCopyParams& params)
{
auto iter = s_encoding_programs.find(params);
@@ -166,6 +81,7 @@ static EncodingProgram& GetOrCreateEncodingShader(const EFBCopyParams& params)
PanicAlert("Failed to compile texture encoding shader.");

program.copy_position_uniform = glGetUniformLocation(program.program.glprogid, "position");
program.y_scale_uniform = glGetUniformLocation(program.program.glprogid, "y_scale");
return s_encoding_programs.emplace(params, program).first->second;
}

@@ -189,8 +105,6 @@ void Init()
FramebufferManager::SetFramebuffer(0);

glGenBuffers(1, &s_PBO);

CreatePrograms();
}

void Shutdown()
@@ -200,9 +114,6 @@ void Shutdown()
glDeleteBuffers(1, &s_PBO);
glDeleteFramebuffers(2, s_texConvFrameBuffer);

s_rgbToYuyvProgram.Destroy();
s_yuyvToRgbProgram.Destroy();

for (auto& program : s_encoding_programs)
program.second.program.Destroy();
s_encoding_programs.clear();
@@ -217,7 +128,7 @@ void Shutdown()
// dst_line_size, writeStride in bytes

static void EncodeToRamUsingShader(GLuint srcTexture, u8* destAddr, u32 dst_line_size,
u32 dstHeight, u32 writeStride, bool linearFilter)
u32 dstHeight, u32 writeStride, bool linearFilter, float y_scale)
{
// switch to texture converter frame buffer
// attach render buffer as color destination
@@ -233,7 +144,7 @@ static void EncodeToRamUsingShader(GLuint srcTexture, u8* destAddr, u32 dst_line
// TODO: This only produces perfect downsampling for 2x IR, other resolutions will need more
// complex down filtering to average all pixels and produce the correct result.
// Also, box filtering won't be correct for anything other than 1x IR
if (linearFilter || g_renderer->GetEFBScale() != 1)
if (linearFilter || g_renderer->GetEFBScale() != 1 || y_scale > 1.0f)
g_sampler_cache->BindLinearSampler(9);
else
g_sampler_cache->BindNearestSampler(9);
@@ -282,73 +193,16 @@ void EncodeToRamFromTexture(u8* dest_ptr, const EFBCopyParams& params, u32 nativ
texconv_shader.program.Bind();
glUniform4i(texconv_shader.copy_position_uniform, src_rect.left, src_rect.top, native_width,
scale_by_half ? 2 : 1);
glUniform1f(texconv_shader.y_scale_uniform, params.y_scale);

const GLuint read_texture = params.depth ?
FramebufferManager::ResolveAndGetDepthTarget(src_rect) :
FramebufferManager::ResolveAndGetRenderTarget(src_rect);

EncodeToRamUsingShader(read_texture, dest_ptr, bytes_per_row, num_blocks_y, memory_stride,
scale_by_half && !params.depth);

FramebufferManager::SetFramebuffer(0);
g_renderer->RestoreAPIState();
}

void EncodeToRamYUYV(GLuint srcTexture, const TargetRectangle& sourceRc, u8* destAddr, u32 dstWidth,
u32 dstStride, u32 dstHeight)
{
g_renderer->ResetAPIState();

s_rgbToYuyvProgram.Bind();

glUniform4f(s_rgbToYuyvUniform_loc, static_cast<float>(sourceRc.left),
static_cast<float>(sourceRc.top), static_cast<float>(sourceRc.right),
static_cast<float>(sourceRc.bottom));

// We enable linear filtering, because the GameCube does filtering in the vertical direction when
// yscale is enabled.
// Otherwise we get jaggies when a game uses yscaling (most PAL games)
EncodeToRamUsingShader(srcTexture, destAddr, dstWidth * 2, dstHeight, dstStride, true);
FramebufferManager::SetFramebuffer(0);
OGLTexture::DisableStage(0);
g_renderer->RestoreAPIState();
}

// Should be scale free.
void DecodeToTexture(u32 xfbAddr, int srcWidth, int srcHeight, GLuint destTexture)
{
u8* srcAddr = Memory::GetPointer(xfbAddr);
if (!srcAddr)
{
WARN_LOG(VIDEO, "Tried to decode from invalid memory address");
return;
}

g_renderer->ResetAPIState(); // reset any game specific settings

OpenGL_BindAttributelessVAO();

// switch to texture converter frame buffer
// attach destTexture as color destination
FramebufferManager::SetFramebuffer(s_texConvFrameBuffer[1]);
FramebufferManager::FramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_ARRAY,
destTexture, 0);

// activate source texture
// set srcAddr as data for source texture
glActiveTexture(GL_TEXTURE9);
glBindTexture(GL_TEXTURE_2D, s_srcTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, srcWidth / 2, srcHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
srcAddr);
g_sampler_cache->BindNearestSampler(9);

glViewport(0, 0, srcWidth, srcHeight);
s_yuyvToRgbProgram.Bind();

glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
scale_by_half && !params.depth, params.y_scale);

FramebufferManager::SetFramebuffer(0);

g_renderer->RestoreAPIState();
}

@@ -20,11 +20,6 @@ namespace TextureConverter
void Init();
void Shutdown();

void EncodeToRamYUYV(GLuint srcTexture, const TargetRectangle& sourceRc, u8* destAddr, u32 dstWidth,
u32 dstStride, u32 dstHeight);

void DecodeToTexture(u32 xfbAddr, int srcWidth, int srcHeight, GLuint destTexture);

// returns size of the encoded data (in bytes)
void EncodeToRamFromTexture(u8* dest_ptr, const EFBCopyParams& params, u32 native_width,
u32 bytes_per_row, u32 num_blocks_y, u32 memory_stride,
@@ -91,6 +91,8 @@ void VideoBackend::InitBackendInfo()
g_Config.backend_info.bSupportsReversedDepthRange = true;
g_Config.backend_info.bSupportsMultithreading = false;
g_Config.backend_info.bSupportsInternalResolutionFrameDumps = true;
g_Config.backend_info.bSupportsCopyToVram = true;
g_Config.backend_info.bForceCopyToRam = false;

// TODO: There is a bug here, if texel buffers are not supported the graphics options
// will show the option when it is not supported. The only way around this would be
@@ -0,0 +1,33 @@
#pragma once

#include "Common/MathUtil.h"

#include <cmath>

namespace SW
{
// Modified from
// http://tech-algorithm.com/articles/nearest-neighbor-image-scaling/
template <typename T>
void CopyRegion(const T* const source, const MathUtil::Rectangle<int>& srcrect, T* destination,
const MathUtil::Rectangle<int>& dstrect)
{
double x_ratio = srcrect.GetWidth() / static_cast<double>(dstrect.GetWidth());
double y_ratio = srcrect.GetHeight() / static_cast<double>(dstrect.GetHeight());
for (int i = 0; i < dstrect.GetHeight(); i++)
{
for (int j = 0; j < dstrect.GetWidth(); j++)
{
int destination_x = j + dstrect.left;
int destination_y = i + dstrect.top;
int destination_offset = (destination_y * dstrect.GetWidth()) + destination_x;

double src_x = std::round(destination_x * x_ratio) + srcrect.left;
double src_y = std::round(destination_y * y_ratio) + srcrect.top;
int src_offset = static_cast<int>((src_y * srcrect.GetWidth()) + src_x);

destination[destination_offset] = source[src_offset];
}
}
}
}