mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-08-11 02:28:51 +00:00
commit
609a17a0cd
329 changed files with 2602 additions and 3736 deletions
|
@ -219,13 +219,13 @@ void DolphinAnalytics::MakePerGameBuilder()
|
|||
builder.AddData("cfg-gfx-multisamples", g_Config.iMultisamples);
|
||||
builder.AddData("cfg-gfx-ssaa", g_Config.bSSAA);
|
||||
builder.AddData("cfg-gfx-anisotropy", g_Config.iMaxAnisotropy);
|
||||
builder.AddData("cfg-gfx-realxfb", g_Config.RealXFBEnabled());
|
||||
builder.AddData("cfg-gfx-virtualxfb", g_Config.VirtualXFBEnabled());
|
||||
builder.AddData("cfg-gfx-vsync", g_Config.bVSync);
|
||||
builder.AddData("cfg-gfx-aspect-ratio", g_Config.iAspectRatio);
|
||||
builder.AddData("cfg-gfx-efb-access", g_Config.bEFBAccessEnable);
|
||||
builder.AddData("cfg-gfx-efb-copy-format-changes", g_Config.bEFBEmulateFormatChanges);
|
||||
builder.AddData("cfg-gfx-efb-copy-ram", !g_Config.bSkipEFBCopyToRam);
|
||||
builder.AddData("cfg-gfx-xfb-copy-ram", !g_Config.bSkipXFBCopyToRam);
|
||||
builder.AddData("cfg-gfx-immediate-xfb", !g_Config.bImmediateXFB);
|
||||
builder.AddData("cfg-gfx-efb-copy-scaled", g_Config.bCopyEFBScaled);
|
||||
builder.AddData("cfg-gfx-internal-resolution", g_Config.iEFBScale);
|
||||
builder.AddData("cfg-gfx-tc-samples", g_Config.iSafeTextureCache_ColorSamples);
|
||||
|
|
|
@ -26,8 +26,6 @@ const ConfigInfo<int> GFX_ASPECT_RATIO{{System::GFX, "Settings", "AspectRatio"},
|
|||
const ConfigInfo<int> GFX_SUGGESTED_ASPECT_RATIO{{System::GFX, "Settings", "SuggestedAspectRatio"},
|
||||
static_cast<int>(ASPECT_AUTO)};
|
||||
const ConfigInfo<bool> GFX_CROP{{System::GFX, "Settings", "Crop"}, false};
|
||||
const ConfigInfo<bool> GFX_USE_XFB{{System::GFX, "Settings", "UseXFB"}, false};
|
||||
const ConfigInfo<bool> GFX_USE_REAL_XFB{{System::GFX, "Settings", "UseRealXFB"}, false};
|
||||
const ConfigInfo<int> GFX_SAFE_TEXTURE_CACHE_COLOR_SAMPLES{
|
||||
{System::GFX, "Settings", "SafeTextureCacheColorSamples"}, 128};
|
||||
const ConfigInfo<bool> GFX_SHOW_FPS{{System::GFX, "Settings", "ShowFPS"}, false};
|
||||
|
@ -45,6 +43,7 @@ const ConfigInfo<bool> GFX_CONVERT_HIRES_TEXTURES{{System::GFX, "Settings", "Con
|
|||
const ConfigInfo<bool> GFX_CACHE_HIRES_TEXTURES{{System::GFX, "Settings", "CacheHiresTextures"},
|
||||
false};
|
||||
const ConfigInfo<bool> GFX_DUMP_EFB_TARGET{{System::GFX, "Settings", "DumpEFBTarget"}, false};
|
||||
const ConfigInfo<bool> GFX_DUMP_XFB_TARGET{{System::GFX, "Settings", "DumpXFBTarget"}, false};
|
||||
const ConfigInfo<bool> GFX_DUMP_FRAMES_AS_IMAGES{{System::GFX, "Settings", "DumpFramesAsImages"},
|
||||
false};
|
||||
const ConfigInfo<bool> GFX_FREE_LOOK{{System::GFX, "Settings", "FreeLook"}, false};
|
||||
|
@ -132,6 +131,9 @@ const ConfigInfo<bool> GFX_HACK_BBOX_PREFER_STENCIL_IMPLEMENTATION{
|
|||
const ConfigInfo<bool> GFX_HACK_FORCE_PROGRESSIVE{{System::GFX, "Hacks", "ForceProgressive"}, true};
|
||||
const ConfigInfo<bool> GFX_HACK_SKIP_EFB_COPY_TO_RAM{{System::GFX, "Hacks", "EFBToTextureEnable"},
|
||||
true};
|
||||
const ConfigInfo<bool> GFX_HACK_SKIP_XFB_COPY_TO_RAM{{System::GFX, "Hacks", "XFBToTextureEnable"},
|
||||
true};
|
||||
const ConfigInfo<bool> GFX_HACK_IMMEDIATE_XFB{{System::GFX, "Hacks", "ImmediateXFBEnable"}, false};
|
||||
const ConfigInfo<bool> GFX_HACK_COPY_EFB_ENABLED{{System::GFX, "Hacks", "EFBScaledCopy"}, true};
|
||||
const ConfigInfo<bool> GFX_HACK_EFB_EMULATE_FORMAT_CHANGES{
|
||||
{System::GFX, "Hacks", "EFBEmulateFormatChanges"}, false};
|
||||
|
|
|
@ -23,8 +23,6 @@ extern const ConfigInfo<bool> GFX_WIDESCREEN_HACK;
|
|||
extern const ConfigInfo<int> GFX_ASPECT_RATIO;
|
||||
extern const ConfigInfo<int> GFX_SUGGESTED_ASPECT_RATIO;
|
||||
extern const ConfigInfo<bool> GFX_CROP;
|
||||
extern const ConfigInfo<bool> GFX_USE_XFB;
|
||||
extern const ConfigInfo<bool> GFX_USE_REAL_XFB;
|
||||
extern const ConfigInfo<int> GFX_SAFE_TEXTURE_CACHE_COLOR_SAMPLES;
|
||||
extern const ConfigInfo<bool> GFX_SHOW_FPS;
|
||||
extern const ConfigInfo<bool> GFX_SHOW_NETPLAY_PING;
|
||||
|
@ -37,6 +35,7 @@ extern const ConfigInfo<bool> GFX_HIRES_TEXTURES;
|
|||
extern const ConfigInfo<bool> GFX_CONVERT_HIRES_TEXTURES;
|
||||
extern const ConfigInfo<bool> GFX_CACHE_HIRES_TEXTURES;
|
||||
extern const ConfigInfo<bool> GFX_DUMP_EFB_TARGET;
|
||||
extern const ConfigInfo<bool> GFX_DUMP_XFB_TARGET;
|
||||
extern const ConfigInfo<bool> GFX_DUMP_FRAMES_AS_IMAGES;
|
||||
extern const ConfigInfo<bool> GFX_FREE_LOOK;
|
||||
extern const ConfigInfo<bool> GFX_USE_FFV1;
|
||||
|
@ -100,6 +99,8 @@ extern const ConfigInfo<bool> GFX_HACK_BBOX_ENABLE;
|
|||
extern const ConfigInfo<bool> GFX_HACK_BBOX_PREFER_STENCIL_IMPLEMENTATION;
|
||||
extern const ConfigInfo<bool> GFX_HACK_FORCE_PROGRESSIVE;
|
||||
extern const ConfigInfo<bool> GFX_HACK_SKIP_EFB_COPY_TO_RAM;
|
||||
extern const ConfigInfo<bool> GFX_HACK_SKIP_XFB_COPY_TO_RAM;
|
||||
extern const ConfigInfo<bool> GFX_HACK_IMMEDIATE_XFB;
|
||||
extern const ConfigInfo<bool> GFX_HACK_COPY_EFB_ENABLED;
|
||||
extern const ConfigInfo<bool> GFX_HACK_EFB_EMULATE_FORMAT_CHANGES;
|
||||
extern const ConfigInfo<bool> GFX_HACK_VERTEX_ROUDING;
|
||||
|
|
|
@ -74,8 +74,6 @@ static const INIToLocationMap& GetINIToLocationMap()
|
|||
{{"Video_Settings", "AspectRatio"}, {Config::GFX_ASPECT_RATIO.location}},
|
||||
{{"Video_Settings", "SuggestedAspectRatio"}, {Config::GFX_SUGGESTED_ASPECT_RATIO.location}},
|
||||
{{"Video_Settings", "Crop"}, {Config::GFX_CROP.location}},
|
||||
{{"Video_Settings", "UseXFB"}, {Config::GFX_USE_XFB.location}},
|
||||
{{"Video_Settings", "UseRealXFB"}, {Config::GFX_USE_REAL_XFB.location}},
|
||||
{{"Video_Settings", "SafeTextureCacheColorSamples"},
|
||||
{Config::GFX_SAFE_TEXTURE_CACHE_COLOR_SAMPLES.location}},
|
||||
{{"Video_Settings", "HiresTextures"}, {Config::GFX_HIRES_TEXTURES.location}},
|
||||
|
@ -109,6 +107,8 @@ static const INIToLocationMap& GetINIToLocationMap()
|
|||
{{"Video_Hacks", "BBoxEnable"}, {Config::GFX_HACK_BBOX_ENABLE.location}},
|
||||
{{"Video_Hacks", "ForceProgressive"}, {Config::GFX_HACK_FORCE_PROGRESSIVE.location}},
|
||||
{{"Video_Hacks", "EFBToTextureEnable"}, {Config::GFX_HACK_SKIP_EFB_COPY_TO_RAM.location}},
|
||||
{{"Video_Hacks", "XFBToTextureEnable"}, {Config::GFX_HACK_SKIP_XFB_COPY_TO_RAM.location}},
|
||||
{{"Video_Hacks", "ImmediateXFBEnable"}, {Config::GFX_HACK_IMMEDIATE_XFB.location}},
|
||||
{{"Video_Hacks", "EFBScaledCopy"}, {Config::GFX_EFB_SCALE.location}},
|
||||
{{"Video_Hacks", "EFBEmulateFormatChanges"},
|
||||
{Config::GFX_HACK_EFB_EMULATE_FORMAT_CHANGES.location}},
|
||||
|
|
|
@ -28,17 +28,17 @@ bool IsSettingSaveable(const Config::ConfigLocation& config_location)
|
|||
// Graphics.Settings
|
||||
|
||||
Config::GFX_WIDESCREEN_HACK.location, Config::GFX_ASPECT_RATIO.location,
|
||||
Config::GFX_CROP.location, Config::GFX_USE_XFB.location, Config::GFX_USE_REAL_XFB.location,
|
||||
Config::GFX_SAFE_TEXTURE_CACHE_COLOR_SAMPLES.location, Config::GFX_SHOW_FPS.location,
|
||||
Config::GFX_SHOW_NETPLAY_PING.location, Config::GFX_SHOW_NETPLAY_MESSAGES.location,
|
||||
Config::GFX_LOG_RENDER_TIME_TO_FILE.location, Config::GFX_OVERLAY_STATS.location,
|
||||
Config::GFX_OVERLAY_PROJ_STATS.location, Config::GFX_DUMP_TEXTURES.location,
|
||||
Config::GFX_HIRES_TEXTURES.location, Config::GFX_CONVERT_HIRES_TEXTURES.location,
|
||||
Config::GFX_CACHE_HIRES_TEXTURES.location, Config::GFX_DUMP_EFB_TARGET.location,
|
||||
Config::GFX_DUMP_FRAMES_AS_IMAGES.location, Config::GFX_FREE_LOOK.location,
|
||||
Config::GFX_USE_FFV1.location, Config::GFX_DUMP_FORMAT.location,
|
||||
Config::GFX_DUMP_CODEC.location, Config::GFX_DUMP_PATH.location,
|
||||
Config::GFX_BITRATE_KBPS.location, Config::GFX_INTERNAL_RESOLUTION_FRAME_DUMPS.location,
|
||||
Config::GFX_CROP.location, Config::GFX_SAFE_TEXTURE_CACHE_COLOR_SAMPLES.location,
|
||||
Config::GFX_SHOW_FPS.location, Config::GFX_SHOW_NETPLAY_PING.location,
|
||||
Config::GFX_SHOW_NETPLAY_MESSAGES.location, Config::GFX_LOG_RENDER_TIME_TO_FILE.location,
|
||||
Config::GFX_OVERLAY_STATS.location, Config::GFX_OVERLAY_PROJ_STATS.location,
|
||||
Config::GFX_DUMP_TEXTURES.location, Config::GFX_HIRES_TEXTURES.location,
|
||||
Config::GFX_CONVERT_HIRES_TEXTURES.location, Config::GFX_CACHE_HIRES_TEXTURES.location,
|
||||
Config::GFX_DUMP_EFB_TARGET.location, Config::GFX_DUMP_FRAMES_AS_IMAGES.location,
|
||||
Config::GFX_FREE_LOOK.location, Config::GFX_USE_FFV1.location,
|
||||
Config::GFX_DUMP_FORMAT.location, Config::GFX_DUMP_CODEC.location,
|
||||
Config::GFX_DUMP_PATH.location, Config::GFX_BITRATE_KBPS.location,
|
||||
Config::GFX_INTERNAL_RESOLUTION_FRAME_DUMPS.location,
|
||||
Config::GFX_ENABLE_GPU_TEXTURE_DECODING.location, Config::GFX_ENABLE_PIXEL_LIGHTING.location,
|
||||
Config::GFX_FAST_DEPTH_CALC.location, Config::GFX_MSAA.location, Config::GFX_SSAA.location,
|
||||
Config::GFX_EFB_SCALE.location, Config::GFX_TEXFMT_OVERLAY_ENABLE.location,
|
||||
|
@ -73,6 +73,7 @@ bool IsSettingSaveable(const Config::ConfigLocation& config_location)
|
|||
Config::GFX_HACK_EFB_ACCESS_ENABLE.location, Config::GFX_HACK_BBOX_ENABLE.location,
|
||||
Config::GFX_HACK_BBOX_PREFER_STENCIL_IMPLEMENTATION.location,
|
||||
Config::GFX_HACK_FORCE_PROGRESSIVE.location, Config::GFX_HACK_SKIP_EFB_COPY_TO_RAM.location,
|
||||
Config::GFX_HACK_SKIP_XFB_COPY_TO_RAM.location, Config::GFX_HACK_IMMEDIATE_XFB.location,
|
||||
Config::GFX_HACK_COPY_EFB_ENABLED.location,
|
||||
Config::GFX_HACK_EFB_EMULATE_FORMAT_CHANGES.location,
|
||||
Config::GFX_HACK_VERTEX_ROUDING.location,
|
||||
|
|
|
@ -39,10 +39,10 @@ static void LoadFromDTM(Config::Layer* config_layer, Movie::DTMHeader* dtm)
|
|||
else
|
||||
config_layer->Set(Config::MAIN_GC_LANGUAGE, static_cast<int>(dtm->language));
|
||||
|
||||
config_layer->Set(Config::GFX_USE_XFB, dtm->bUseXFB);
|
||||
config_layer->Set(Config::GFX_USE_REAL_XFB, dtm->bUseRealXFB);
|
||||
config_layer->Set(Config::GFX_HACK_EFB_ACCESS_ENABLE, dtm->bEFBAccessEnable);
|
||||
config_layer->Set(Config::GFX_HACK_SKIP_EFB_COPY_TO_RAM, dtm->bSkipEFBCopyToRam);
|
||||
config_layer->Set(Config::GFX_HACK_SKIP_XFB_COPY_TO_RAM, dtm->bSkipXFBCopyToRam);
|
||||
config_layer->Set(Config::GFX_HACK_IMMEDIATE_XFB, dtm->bImmediateXFB);
|
||||
config_layer->Set(Config::GFX_HACK_EFB_EMULATE_FORMAT_CHANGES, dtm->bEFBEmulateFormatChanges);
|
||||
}
|
||||
|
||||
|
@ -62,10 +62,10 @@ void SaveToDTM(Movie::DTMHeader* dtm)
|
|||
else
|
||||
dtm->language = Config::Get(Config::MAIN_GC_LANGUAGE);
|
||||
|
||||
dtm->bUseXFB = Config::Get(Config::GFX_USE_XFB);
|
||||
dtm->bUseRealXFB = Config::Get(Config::GFX_USE_REAL_XFB);
|
||||
dtm->bEFBAccessEnable = Config::Get(Config::GFX_HACK_EFB_ACCESS_ENABLE);
|
||||
dtm->bSkipEFBCopyToRam = Config::Get(Config::GFX_HACK_SKIP_EFB_COPY_TO_RAM);
|
||||
dtm->bSkipXFBCopyToRam = Config::Get(Config::GFX_HACK_SKIP_XFB_COPY_TO_RAM);
|
||||
dtm->bImmediateXFB = Config::Get(Config::GFX_HACK_IMMEDIATE_XFB);
|
||||
dtm->bEFBEmulateFormatChanges = Config::Get(Config::GFX_HACK_EFB_EMULATE_FORMAT_CHANGES);
|
||||
|
||||
// This never used the regular config
|
||||
|
|
|
@ -393,7 +393,7 @@ static void CpuThread()
|
|||
s_is_started = false;
|
||||
|
||||
if (!_CoreParameter.bCPUThread)
|
||||
g_video_backend->Video_Cleanup();
|
||||
g_video_backend->Video_CleanupShared();
|
||||
|
||||
if (_CoreParameter.bFastmem)
|
||||
EMM::UninstallExceptionHandler();
|
||||
|
@ -445,7 +445,7 @@ static void FifoPlayerThread()
|
|||
}
|
||||
|
||||
if (!_CoreParameter.bCPUThread)
|
||||
g_video_backend->Video_Cleanup();
|
||||
g_video_backend->Video_CleanupShared();
|
||||
}
|
||||
|
||||
// Initialize and create emulation thread
|
||||
|
@ -654,7 +654,7 @@ static void EmuThread(std::unique_ptr<BootParameters> boot)
|
|||
INFO_LOG(CONSOLE, "%s", StopMessage(true, "CPU thread stopped.").c_str());
|
||||
|
||||
if (core_parameter.bCPUThread)
|
||||
g_video_backend->Video_Cleanup();
|
||||
g_video_backend->Video_CleanupShared();
|
||||
|
||||
// If we shut down normally, the stop message does not need to be triggered.
|
||||
stop_message_guard.Dismiss();
|
||||
|
|
|
@ -72,6 +72,11 @@ FifoDataFile::FifoDataFile() = default;
|
|||
|
||||
FifoDataFile::~FifoDataFile() = default;
|
||||
|
||||
bool FifoDataFile::ShouldGenerateFakeVIUpdates() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FifoDataFile::HasBrokenEFBCopies() const
|
||||
{
|
||||
return m_Version < 2;
|
||||
|
|
|
@ -60,6 +60,7 @@ public:
|
|||
void SetIsWii(bool isWii);
|
||||
bool GetIsWii() const;
|
||||
bool HasBrokenEFBCopies() const;
|
||||
bool ShouldGenerateFakeVIUpdates() const;
|
||||
|
||||
u32* GetBPMem() { return m_BPMem; }
|
||||
u32* GetCPMem() { return m_CPMem; }
|
||||
|
|
|
@ -162,6 +162,16 @@ std::unique_ptr<CPUCoreBase> FifoPlayer::GetCPUCore()
|
|||
return std::make_unique<CPUCore>(this);
|
||||
}
|
||||
|
||||
bool FifoPlayer::IsRunningWithFakeVideoInterfaceUpdates() const
|
||||
{
|
||||
if (!m_File || m_File->GetFrameCount() == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return m_File->ShouldGenerateFakeVIUpdates();
|
||||
}
|
||||
|
||||
u32 FifoPlayer::GetFrameObjectCount() const
|
||||
{
|
||||
if (m_CurrentFrame < m_FrameInfo.size())
|
||||
|
|
|
@ -94,6 +94,8 @@ public:
|
|||
void SetFrameWrittenCallback(CallbackFunc callback) { m_FrameWrittenCb = callback; }
|
||||
static FifoPlayer& GetInstance();
|
||||
|
||||
bool IsRunningWithFakeVideoInterfaceUpdates() const;
|
||||
|
||||
private:
|
||||
class CPUCore;
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/CoreTiming.h"
|
||||
#include "Core/FifoPlayer/FifoPlayer.h"
|
||||
#include "Core/HW/MMIO.h"
|
||||
#include "Core/HW/ProcessorInterface.h"
|
||||
#include "Core/HW/SI/SI.h"
|
||||
|
@ -549,8 +550,9 @@ float GetAspectRatio()
|
|||
|
||||
// 5. Calculate the final ratio and scale to 4:3
|
||||
float ratio = horizontal_active_ratio / vertical_active_ratio;
|
||||
if (std::isnormal(
|
||||
ratio)) // Check we have a sane ratio and haven't propagated any infs/nans/zeros
|
||||
bool running_fifo_log = FifoPlayer::GetInstance().IsRunningWithFakeVideoInterfaceUpdates();
|
||||
if (std::isnormal(ratio) && // Check we have a sane ratio without any infs/nans/zeros
|
||||
!running_fifo_log) // we don't know the correct ratio for fifos
|
||||
return ratio * (4.0f / 3.0f); // Scale to 4:3
|
||||
else
|
||||
return (4.0f / 3.0f); // VI isn't initialized correctly, just return 4:3 instead
|
||||
|
@ -775,4 +777,44 @@ void Update(u64 ticks)
|
|||
UpdateInterrupts();
|
||||
}
|
||||
|
||||
// Create a fake VI mode for a fifolog
|
||||
void FakeVIUpdate(u32 xfb_address, u32 fb_width, u32 fb_height)
|
||||
{
|
||||
u32 fb_stride = fb_width;
|
||||
|
||||
bool interlaced = fb_height > 480 / 2;
|
||||
if (interlaced)
|
||||
{
|
||||
fb_height = fb_height / 2;
|
||||
fb_stride = fb_stride * 2;
|
||||
}
|
||||
|
||||
m_XFBInfoTop.POFF = 1;
|
||||
m_XFBInfoBottom.POFF = 1;
|
||||
m_VerticalTimingRegister.ACV = fb_height;
|
||||
m_VerticalTimingRegister.EQU = 6;
|
||||
m_VBlankTimingOdd.PRB = 502 - fb_height * 2;
|
||||
m_VBlankTimingOdd.PSB = 5;
|
||||
m_VBlankTimingEven.PRB = 503 - fb_height * 2;
|
||||
m_VBlankTimingEven.PSB = 4;
|
||||
m_PictureConfiguration.WPL = fb_width / 16;
|
||||
m_PictureConfiguration.STD = fb_stride / 16;
|
||||
|
||||
UpdateParameters();
|
||||
|
||||
u32 total_halflines = GetHalfLinesPerEvenField() + GetHalfLinesPerOddField();
|
||||
|
||||
if ((s_half_line_count - s_even_field_first_hl) % total_halflines <
|
||||
(s_half_line_count - s_odd_field_first_hl) % total_halflines)
|
||||
{
|
||||
// Even/Bottom field is next.
|
||||
m_XFBInfoBottom.FBB = interlaced ? (xfb_address + fb_width * 2) >> 5 : xfb_address >> 5;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Odd/Top field is next
|
||||
m_XFBInfoTop.FBB = (xfb_address >> 5);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
|
@ -373,4 +373,7 @@ u32 GetTicksPerField();
|
|||
// result by 1.33333..
|
||||
float GetAspectRatio();
|
||||
|
||||
// Create a fake VI mode for a fifolog
|
||||
void FakeVIUpdate(u32 xfb_address, u32 fb_width, u32 fb_height);
|
||||
|
||||
} // namespace VideoInterface
|
||||
|
|
|
@ -72,6 +72,8 @@ const std::string hotkey_labels[] = {
|
|||
_trans("Toggle Crop"),
|
||||
_trans("Toggle Aspect Ratio"),
|
||||
_trans("Toggle EFB Copies"),
|
||||
_trans("Toggle XFB Copies"),
|
||||
_trans("Toggle XFB Immediate Mode"),
|
||||
_trans("Toggle Fog"),
|
||||
_trans("Toggle Texture Dumping"),
|
||||
_trans("Toggle Custom Textures"),
|
||||
|
|
|
@ -70,6 +70,8 @@ enum Hotkey
|
|||
HK_TOGGLE_CROP,
|
||||
HK_TOGGLE_AR,
|
||||
HK_TOGGLE_EFBCOPIES,
|
||||
HK_TOGGLE_XFBCOPIES,
|
||||
HK_TOGGLE_IMMEDIATE_XFB,
|
||||
HK_TOGGLE_FOG,
|
||||
HK_TOGGLE_DUMPTEXTURES,
|
||||
HK_TOGGLE_TEXTURES,
|
||||
|
|
|
@ -1367,9 +1367,9 @@ void SetGraphicsConfig()
|
|||
{
|
||||
g_Config.bEFBAccessEnable = tmpHeader.bEFBAccessEnable;
|
||||
g_Config.bSkipEFBCopyToRam = tmpHeader.bSkipEFBCopyToRam;
|
||||
g_Config.bSkipXFBCopyToRam = tmpHeader.bSkipXFBCopyToRam;
|
||||
g_Config.bImmediateXFB = tmpHeader.bImmediateXFB;
|
||||
g_Config.bEFBEmulateFormatChanges = tmpHeader.bEFBEmulateFormatChanges;
|
||||
g_Config.bUseXFB = tmpHeader.bUseXFB;
|
||||
g_Config.bUseRealXFB = tmpHeader.bUseRealXFB;
|
||||
}
|
||||
|
||||
// NOTE: EmuThread / Host Thread
|
||||
|
|
|
@ -87,10 +87,10 @@ struct DTMHeader
|
|||
bool bEFBAccessEnable;
|
||||
bool bEFBCopyEnable;
|
||||
bool bSkipEFBCopyToRam;
|
||||
bool bSkipXFBCopyToRam;
|
||||
bool bImmediateXFB;
|
||||
bool bEFBCopyCacheEnable;
|
||||
bool bEFBEmulateFormatChanges;
|
||||
bool bUseXFB;
|
||||
bool bUseRealXFB;
|
||||
u8 memcards; // Memcards inserted (from least to most significant, the bits are slot A and B)
|
||||
bool bClearSave; // Create a new memory card when playing back a movie if true
|
||||
u8 bongos; // Bongos plugged in (from least to most significant, the bits are ports 1-4)
|
||||
|
|
|
@ -20,7 +20,6 @@ HacksWidget::HacksWidget(GraphicsWindow* parent) : GraphicsWidget(parent)
|
|||
CreateWidgets();
|
||||
LoadSettings();
|
||||
ConnectWidgets();
|
||||
OnXFBToggled();
|
||||
AddDescriptions();
|
||||
}
|
||||
|
||||
|
@ -68,13 +67,12 @@ void HacksWidget::CreateWidgets()
|
|||
auto* xfb_layout = new QGridLayout();
|
||||
xfb_box->setLayout(xfb_layout);
|
||||
|
||||
m_disable_xfb = new GraphicsBool(tr("Disable"), Config::GFX_USE_XFB, true);
|
||||
m_real_xfb = new GraphicsBoolEx(tr("Real"), Config::GFX_USE_REAL_XFB, false);
|
||||
m_virtual_xfb = new GraphicsBoolEx(tr("Virtual"), Config::GFX_USE_REAL_XFB, true);
|
||||
m_store_xfb_copies = new GraphicsBool(tr("Store XFB Copies to Texture Only"),
|
||||
Config::GFX_HACK_SKIP_XFB_COPY_TO_RAM);
|
||||
m_immediate_xfb = new GraphicsBool(tr("Immediately Present XFB"), Config::GFX_HACK_IMMEDIATE_XFB);
|
||||
|
||||
xfb_layout->addWidget(m_store_xfb_copies, 1, 0);
|
||||
|
||||
xfb_layout->addWidget(m_disable_xfb, 0, 0);
|
||||
xfb_layout->addWidget(m_virtual_xfb, 0, 1);
|
||||
xfb_layout->addWidget(m_real_xfb, 0, 2);
|
||||
// Other
|
||||
auto* other_box = new QGroupBox(tr("Other"));
|
||||
auto* other_layout = new QGridLayout();
|
||||
|
@ -101,16 +99,9 @@ void HacksWidget::CreateWidgets()
|
|||
|
||||
void HacksWidget::ConnectWidgets()
|
||||
{
|
||||
connect(m_disable_xfb, &QCheckBox::toggled, this, &HacksWidget::OnXFBToggled);
|
||||
connect(m_accuracy, &QSlider::valueChanged, [this](int) { SaveSettings(); });
|
||||
}
|
||||
|
||||
void HacksWidget::OnXFBToggled()
|
||||
{
|
||||
m_real_xfb->setEnabled(!m_disable_xfb->isChecked());
|
||||
m_virtual_xfb->setEnabled(!m_disable_xfb->isChecked());
|
||||
}
|
||||
|
||||
void HacksWidget::LoadSettings()
|
||||
{
|
||||
auto samples = Config::Get(Config::GFX_SAFE_TEXTURE_CACHE_COLOR_SAMPLES);
|
||||
|
@ -179,20 +170,18 @@ void HacksWidget::AddDescriptions()
|
|||
"from RAM.\nLower accuracies cause in-game text to appear garbled in certain "
|
||||
"games.\n\nIf unsure, use the rightmost value.");
|
||||
|
||||
static const char* TR_DISABLE_XFB_DESCRIPTION = QT_TR_NOOP(
|
||||
"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 const char* TR_VIRTUAL_XFB_DESCRIPTION = QT_TR_NOOP(
|
||||
"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 const char* TR_STORE_XFB_TO_TEXTURE_DESCRIPTION = QT_TR_NOOP(
|
||||
"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 const char* TR_REAL_XFB_DESCRIPTION =
|
||||
QT_TR_NOOP("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 const char* TR_IMMEDIATE_XFB_DESCRIPTION =
|
||||
QT_TR_NOOP("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 const char* TR_GPU_DECODING_DESCRIPTION =
|
||||
QT_TR_NOOP("Enables texture decoding using the GPU instead of the CPU. This may result in "
|
||||
|
@ -215,9 +204,8 @@ void HacksWidget::AddDescriptions()
|
|||
AddDescription(m_ignore_format_changes, TR_IGNORE_FORMAT_CHANGE_DESCRIPTION);
|
||||
AddDescription(m_store_efb_copies, TR_STORE_EFB_TO_TEXTURE_DESCRIPTION);
|
||||
AddDescription(m_accuracy, TR_ACCUARCY_DESCRIPTION);
|
||||
AddDescription(m_disable_xfb, TR_DISABLE_XFB_DESCRIPTION);
|
||||
AddDescription(m_virtual_xfb, TR_VIRTUAL_XFB_DESCRIPTION);
|
||||
AddDescription(m_real_xfb, TR_REAL_XFB_DESCRIPTION);
|
||||
AddDescription(m_store_xfb_copies, TR_STORE_XFB_TO_TEXTURE_DESCRIPTION);
|
||||
AddDescription(m_immediate_xfb, TR_STORE_XFB_TO_TEXTURE_DESCRIPTION);
|
||||
AddDescription(m_gpu_texture_decoding, TR_GPU_DECODING_DESCRIPTION);
|
||||
AddDescription(m_fast_depth_calculation, TR_FAST_DEPTH_CALC_DESCRIPTION);
|
||||
AddDescription(m_disable_bounding_box, TR_DISABLE_BOUNDINGBOX_DESCRIPTION);
|
||||
|
|
|
@ -21,8 +21,6 @@ private:
|
|||
void LoadSettings() override;
|
||||
void SaveSettings() override;
|
||||
|
||||
void OnXFBToggled();
|
||||
|
||||
// EFB
|
||||
QCheckBox* m_skip_efb_cpu;
|
||||
QCheckBox* m_ignore_format_changes;
|
||||
|
@ -33,9 +31,8 @@ private:
|
|||
QCheckBox* m_gpu_texture_decoding;
|
||||
|
||||
// External Framebuffer
|
||||
QCheckBox* m_disable_xfb;
|
||||
QRadioButton* m_virtual_xfb;
|
||||
QRadioButton* m_real_xfb;
|
||||
QCheckBox* m_store_xfb_copies;
|
||||
QCheckBox* m_immediate_xfb;
|
||||
|
||||
// Other
|
||||
QCheckBox* m_fast_depth_calculation;
|
||||
|
|
|
@ -38,12 +38,10 @@ void SoftwareRendererWidget::CreateWidgets()
|
|||
auto* rendering_box = new QGroupBox(tr("Rendering"));
|
||||
auto* rendering_layout = new QGridLayout();
|
||||
m_backend_combo = new QComboBox();
|
||||
m_bypass_xfb = new GraphicsBool(tr("Bypass XFB"), Config::GFX_USE_XFB, true);
|
||||
|
||||
rendering_box->setLayout(rendering_layout);
|
||||
rendering_layout->addWidget(new QLabel(tr("Backend:")), 1, 1);
|
||||
rendering_layout->addWidget(m_backend_combo, 1, 2);
|
||||
rendering_layout->addWidget(m_bypass_xfb, 2, 1);
|
||||
|
||||
for (const auto& backend : g_available_video_backends)
|
||||
m_backend_combo->addItem(tr(backend->GetDisplayName().c_str()));
|
||||
|
@ -156,11 +154,6 @@ void SoftwareRendererWidget::AddDescriptions()
|
|||
"backend, so for the best emulation experience it's recommended to try both and "
|
||||
"choose the one that's less problematic.\n\nIf unsure, select OpenGL.");
|
||||
|
||||
static const char* TR_BYPASS_XFB_DESCRIPTION = QT_TR_NOOP(
|
||||
"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 const char* TR_SHOW_STATISTICS_DESCRIPTION =
|
||||
QT_TR_NOOP("Show various rendering statistics.\n\nIf unsure, leave this unchecked.");
|
||||
|
||||
|
@ -169,7 +162,6 @@ void SoftwareRendererWidget::AddDescriptions()
|
|||
"this unchecked.");
|
||||
|
||||
AddDescription(m_backend_combo, TR_BACKEND_DESCRIPTION);
|
||||
AddDescription(m_bypass_xfb, TR_BYPASS_XFB_DESCRIPTION);
|
||||
AddDescription(m_enable_statistics, TR_SHOW_STATISTICS_DESCRIPTION);
|
||||
AddDescription(m_dump_textures, TR_DUMP_TEXTURES_DESCRIPTION);
|
||||
}
|
||||
|
|
|
@ -29,7 +29,6 @@ private:
|
|||
void AddDescriptions();
|
||||
|
||||
QComboBox* m_backend_combo;
|
||||
QCheckBox* m_bypass_xfb;
|
||||
QCheckBox* m_enable_statistics;
|
||||
QCheckBox* m_dump_textures;
|
||||
QCheckBox* m_dump_objects;
|
||||
|
|
|
@ -224,6 +224,10 @@ void HotkeyScheduler::Run()
|
|||
g_Config.iAspectRatio = (g_Config.iAspectRatio + 1) & 3;
|
||||
if (IsHotkey(HK_TOGGLE_EFBCOPIES))
|
||||
g_Config.bSkipEFBCopyToRam = !g_Config.bSkipEFBCopyToRam;
|
||||
if (IsHotkey(HK_TOGGLE_XFBCOPIES))
|
||||
g_Config.bSkipXFBCopyToRam = !g_Config.bSkipXFBCopyToRam;
|
||||
if (IsHotkey(HK_TOGGLE_IMMEDIATE_XFB))
|
||||
g_Config.bImmediateXFB = !g_Config.bImmediateXFB;
|
||||
if (IsHotkey(HK_TOGGLE_FOG))
|
||||
g_Config.bDisableFog = !g_Config.bDisableFog;
|
||||
if (IsHotkey(HK_TOGGLE_DUMPTEXTURES))
|
||||
|
|
|
@ -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);
|
||||
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);
|
||||
|
||||
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(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);
|
||||
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);
|
||||
|
||||
// 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);
|
||||
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
|
||||
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(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};
|
||||
}
|
||||
|
||||
return encode_result;
|
||||
void DXTexture::Unmap()
|
||||
{
|
||||
if (!m_staging_texture)
|
||||
return;
|
||||
|
||||
D3D::context->Unmap(m_staging_texture, 0);
|
||||
}
|
||||
|
||||
void DXTexture::CopyRectangleFromTexture(const AbstractTexture* source,
|
||||
|
|
|
@ -19,7 +19,7 @@ public:
|
|||
~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 @@ public:
|
|||
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 @@ public:
|
|||
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,6 +129,7 @@ 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);
|
||||
|
||||
|
@ -131,7 +137,7 @@ void PSTextureEncoder::Encode(u8* dst, const EFBCopyParams& params, u32 native_w
|
|||
// 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 @@ public:
|
|||
|
||||
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;
|
||||
|
|
|
@ -1,166 +0,0 @@
|
|||
// Copyright 2011 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "VideoBackends/D3D/Television.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "VideoBackends/D3D/D3DBase.h"
|
||||
#include "VideoBackends/D3D/D3DShader.h"
|
||||
#include "VideoBackends/D3D/D3DState.h"
|
||||
#include "VideoBackends/D3D/D3DUtil.h"
|
||||
#include "VideoBackends/D3D/VertexShaderCache.h"
|
||||
#include "VideoCommon/VideoCommon.h"
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
|
||||
namespace DX11
|
||||
{
|
||||
static const char YUYV_DECODER_PS[] =
|
||||
"// dolphin-emu YUYV decoder pixel shader\n"
|
||||
|
||||
"Texture2D Tex0 : register(t0);\n"
|
||||
"sampler Samp0 : register(s0);\n"
|
||||
|
||||
"static const float3x3 YCBCR_TO_RGB = float3x3(\n"
|
||||
"1.164, 0.000, 1.596,\n"
|
||||
"1.164, -0.392, -0.813,\n"
|
||||
"1.164, 2.017, 0.000\n"
|
||||
");\n"
|
||||
|
||||
"void main(out float4 ocol0 : SV_Target, in float4 pos : SV_Position, in float2 uv0 : "
|
||||
"TEXCOORD0)\n"
|
||||
"{\n"
|
||||
"float3 sample = Tex0.Sample(Samp0, uv0).rgb;\n"
|
||||
|
||||
// GameCube/Wii XFB data is in YUYV format with ITU-R Rec. BT.601 color
|
||||
// primaries, compressed to the range Y in 16..235, U and V in 16..240.
|
||||
// We want to convert it to RGB format with sRGB color primaries, with
|
||||
// range 0..255.
|
||||
|
||||
// Recover RGB components
|
||||
"float3 yuv_601_sub = sample.grb - float3(16.0/255.0, 128.0/255.0, 128.0/255.0);\n"
|
||||
"float3 rgb_601 = mul(YCBCR_TO_RGB, yuv_601_sub);\n"
|
||||
|
||||
// If we were really obsessed with accuracy, we would correct for the
|
||||
// differing color primaries between BT.601 and sRGB. However, this may not
|
||||
// be worth the trouble because:
|
||||
// - BT.601 defines two sets of primaries: one for NTSC and one for PAL.
|
||||
// - sRGB's color primaries are actually an intermediate between BT.601's
|
||||
// NTSC and PAL primaries.
|
||||
// - If users even noticed any difference at all, they would be confused by
|
||||
// the slightly-different colors in the NTSC and PAL versions of the same
|
||||
// game.
|
||||
// - Even the game designers probably don't pay close attention to this
|
||||
// stuff.
|
||||
// Still, instructions on how to do it can be found at
|
||||
// <http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html#RTFToC20>
|
||||
|
||||
"ocol0 = float4(rgb_601, 1);\n"
|
||||
"}\n";
|
||||
|
||||
Television::Television() : m_yuyvTexture(nullptr), m_yuyvTextureSRV(nullptr), m_pShader(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void Television::Init()
|
||||
{
|
||||
HRESULT hr;
|
||||
|
||||
// Create YUYV texture for real XFB mode
|
||||
|
||||
// Initialize the texture with YCbCr black
|
||||
//
|
||||
// Some games use narrower XFB widths (Nintendo titles are fond of 608),
|
||||
// so the sampler's BorderColor won't cover the right side
|
||||
// (see sampler state below)
|
||||
const unsigned int MAX_XFB_SIZE = 2 * (MAX_XFB_WIDTH)*MAX_XFB_HEIGHT;
|
||||
std::vector<u8> fill(MAX_XFB_SIZE);
|
||||
for (size_t i = 0; i < MAX_XFB_SIZE / sizeof(u32); ++i)
|
||||
reinterpret_cast<u32*>(fill.data())[i] = 0x80108010;
|
||||
D3D11_SUBRESOURCE_DATA srd = {fill.data(), 2 * (MAX_XFB_WIDTH), 0};
|
||||
|
||||
// This texture format is designed for YUYV data.
|
||||
D3D11_TEXTURE2D_DESC t2dd =
|
||||
CD3D11_TEXTURE2D_DESC(DXGI_FORMAT_G8R8_G8B8_UNORM, MAX_XFB_WIDTH, MAX_XFB_HEIGHT, 1, 1);
|
||||
hr = D3D::device->CreateTexture2D(&t2dd, &srd, &m_yuyvTexture);
|
||||
CHECK(SUCCEEDED(hr), "create tv yuyv texture");
|
||||
D3D::SetDebugObjectName(m_yuyvTexture, "tv yuyv texture");
|
||||
|
||||
// Create shader resource view for YUYV texture
|
||||
|
||||
D3D11_SHADER_RESOURCE_VIEW_DESC srvd = CD3D11_SHADER_RESOURCE_VIEW_DESC(
|
||||
m_yuyvTexture, D3D11_SRV_DIMENSION_TEXTURE2D, DXGI_FORMAT_G8R8_G8B8_UNORM);
|
||||
hr = D3D::device->CreateShaderResourceView(m_yuyvTexture, &srvd, &m_yuyvTextureSRV);
|
||||
CHECK(SUCCEEDED(hr), "create tv yuyv texture srv");
|
||||
D3D::SetDebugObjectName(m_yuyvTextureSRV, "tv yuyv texture srv");
|
||||
|
||||
// Create YUYV-decoding pixel shader
|
||||
|
||||
m_pShader = D3D::CompileAndCreatePixelShader(YUYV_DECODER_PS);
|
||||
CHECK(m_pShader != nullptr, "compile and create yuyv decoder pixel shader");
|
||||
D3D::SetDebugObjectName(m_pShader, "yuyv decoder pixel shader");
|
||||
|
||||
// Create sampler state and set border color
|
||||
//
|
||||
// The default sampler border color of { 0.f, 0.f, 0.f, 0.f }
|
||||
// creates a green border around the image - see issue 6483
|
||||
// (remember, the XFB is being interpreted as YUYV, and 0,0,0,0
|
||||
// is actually two green pixels in YUYV - black should be 16,128,16,128,
|
||||
// but we reverse the order to match DXGI_FORMAT_G8R8_G8B8_UNORM's ordering)
|
||||
float border[4] = {128.0f / 255.0f, 16.0f / 255.0f, 128.0f / 255.0f, 16.0f / 255.0f};
|
||||
D3D11_SAMPLER_DESC samDesc = CD3D11_SAMPLER_DESC(
|
||||
D3D11_FILTER_MIN_MAG_MIP_LINEAR, D3D11_TEXTURE_ADDRESS_BORDER, D3D11_TEXTURE_ADDRESS_BORDER,
|
||||
D3D11_TEXTURE_ADDRESS_BORDER, 0.f, 1, D3D11_COMPARISON_ALWAYS, border, 0.f, 0.f);
|
||||
hr = D3D::device->CreateSamplerState(&samDesc, &m_samplerState);
|
||||
CHECK(SUCCEEDED(hr), "create yuyv decoder sampler state");
|
||||
D3D::SetDebugObjectName(m_samplerState, "yuyv decoder sampler state");
|
||||
}
|
||||
|
||||
void Television::Shutdown()
|
||||
{
|
||||
SAFE_RELEASE(m_pShader);
|
||||
SAFE_RELEASE(m_yuyvTextureSRV);
|
||||
SAFE_RELEASE(m_yuyvTexture);
|
||||
SAFE_RELEASE(m_samplerState);
|
||||
}
|
||||
|
||||
void Television::Submit(u32 xfbAddr, u32 stride, u32 width, u32 height)
|
||||
{
|
||||
m_curAddr = xfbAddr;
|
||||
m_curWidth = width;
|
||||
m_curHeight = height;
|
||||
|
||||
// Load data from GameCube RAM to YUYV texture
|
||||
u8* yuyvSrc = Memory::GetPointer(xfbAddr);
|
||||
D3D11_BOX box = CD3D11_BOX(0, 0, 0, stride, height, 1);
|
||||
D3D::context->UpdateSubresource(m_yuyvTexture, 0, &box, yuyvSrc, 2 * stride, 2 * stride * height);
|
||||
}
|
||||
|
||||
void Television::Render()
|
||||
{
|
||||
if (g_ActiveConfig.bUseRealXFB && g_ActiveConfig.bUseXFB)
|
||||
{
|
||||
// Use real XFB mode
|
||||
// TODO: If this is the lower field, render at a vertical offset of 1
|
||||
// line down. We could even consider implementing a deinterlacing
|
||||
// algorithm.
|
||||
|
||||
D3D11_RECT sourceRc = CD3D11_RECT(0, 0, int(m_curWidth), int(m_curHeight));
|
||||
|
||||
D3D::stateman->SetSampler(0, m_samplerState);
|
||||
|
||||
D3D::drawShadedTexQuad(m_yuyvTextureSRV, &sourceRc, MAX_XFB_WIDTH, MAX_XFB_HEIGHT, m_pShader,
|
||||
VertexShaderCache::GetSimpleVertexShader(),
|
||||
VertexShaderCache::GetSimpleInputLayout());
|
||||
}
|
||||
else if (g_ActiveConfig.bUseXFB)
|
||||
{
|
||||
// Use virtual XFB mode
|
||||
|
||||
// TODO: Eventually, Television should render the Virtual XFB mode
|
||||
// display as well.
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
// Copyright 2011 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
struct ID3D11Texture2D;
|
||||
struct ID3D11ShaderResourceView;
|
||||
struct ID3D11PixelShader;
|
||||
struct ID3D11SamplerState;
|
||||
|
||||
namespace DX11
|
||||
{
|
||||
class Television
|
||||
{
|
||||
public:
|
||||
Television();
|
||||
|
||||
void Init();
|
||||
void Shutdown();
|
||||
|
||||
// Submit video data to be drawn. This will change the current state of the
|
||||
// TV. xfbAddr points to YUYV data stored in GameCube/Wii RAM, but the XFB
|
||||
// may be virtualized when rendering so the RAM may not actually be read.
|
||||
void Submit(u32 xfbAddr, u32 stride, u32 width, u32 height);
|
||||
|
||||
// Render the current state of the TV.
|
||||
void Render();
|
||||
|
||||
private:
|
||||
// Properties of last Submit call
|
||||
u32 m_curAddr;
|
||||
u32 m_curWidth;
|
||||
u32 m_curHeight;
|
||||
|
||||
// Used for real XFB mode
|
||||
|
||||
ID3D11Texture2D* m_yuyvTexture;
|
||||
ID3D11ShaderResourceView* m_yuyvTextureSRV;
|
||||
ID3D11PixelShader* m_pShader;
|
||||
ID3D11SamplerState* m_samplerState;
|
||||
};
|
||||
}
|
|
@ -1,367 +0,0 @@
|
|||
// Copyright 2011 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "VideoBackends/D3D/XFBEncoder.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "VideoBackends/D3D/D3DBase.h"
|
||||
#include "VideoBackends/D3D/D3DBlob.h"
|
||||
#include "VideoBackends/D3D/D3DShader.h"
|
||||
#include "VideoBackends/D3D/D3DState.h"
|
||||
#include "VideoBackends/D3D/FramebufferManager.h"
|
||||
#include "VideoBackends/D3D/Render.h"
|
||||
|
||||
namespace DX11
|
||||
{
|
||||
union XFBEncodeParams
|
||||
{
|
||||
struct
|
||||
{
|
||||
FLOAT Width; // Width and height of encoded XFB in luma pixels
|
||||
FLOAT Height;
|
||||
FLOAT TexLeft; // Normalized tex coordinates of XFB source area in EFB texture
|
||||
FLOAT TexTop;
|
||||
FLOAT TexRight;
|
||||
FLOAT TexBottom;
|
||||
FLOAT Gamma;
|
||||
};
|
||||
// Constant buffers must be a multiple of 16 bytes in size
|
||||
u8 pad[32]; // Pad to the next multiple of 16
|
||||
};
|
||||
|
||||
static const char XFB_ENCODE_VS[] =
|
||||
"// dolphin-emu XFB encoder vertex shader\n"
|
||||
|
||||
"cbuffer cbParams : register(b0)\n"
|
||||
"{\n"
|
||||
"struct\n" // Should match XFBEncodeParams above
|
||||
"{\n"
|
||||
"float Width;\n"
|
||||
"float Height;\n"
|
||||
"float TexLeft;\n"
|
||||
"float TexTop;\n"
|
||||
"float TexRight;\n"
|
||||
"float TexBottom;\n"
|
||||
"float Gamma;\n"
|
||||
"} Params;\n"
|
||||
"}\n"
|
||||
|
||||
"struct Output\n"
|
||||
"{\n"
|
||||
"float4 Pos : SV_Position;\n"
|
||||
"float2 Coord : ENCODECOORD;\n"
|
||||
"};\n"
|
||||
|
||||
"Output main(in float2 Pos : POSITION)\n"
|
||||
"{\n"
|
||||
"Output result;\n"
|
||||
"result.Pos = float4(2*Pos.x-1, -2*Pos.y+1, 0, 1);\n"
|
||||
"result.Coord = Pos * float2(floor(Params.Width/2), Params.Height);\n"
|
||||
"return result;\n"
|
||||
"}\n";
|
||||
|
||||
static const char XFB_ENCODE_PS[] =
|
||||
"// dolphin-emu XFB encoder pixel shader\n"
|
||||
|
||||
"cbuffer cbParams : register(b0)\n"
|
||||
"{\n"
|
||||
"struct\n" // Should match XFBEncodeParams above
|
||||
"{\n"
|
||||
"float Width;\n"
|
||||
"float Height;\n"
|
||||
"float TexLeft;\n"
|
||||
"float TexTop;\n"
|
||||
"float TexRight;\n"
|
||||
"float TexBottom;\n"
|
||||
"float Gamma;\n"
|
||||
"} Params;\n"
|
||||
"}\n"
|
||||
|
||||
"Texture2DArray EFBTexture : register(t0);\n"
|
||||
"sampler EFBSampler : register(s0);\n"
|
||||
|
||||
// GameCube/Wii uses the BT.601 standard algorithm for converting to YCbCr; see
|
||||
// <http://www.equasys.de/colorconversion.html#YCbCr-RGBColorFormatConversion>
|
||||
"static const float3x4 RGB_TO_YCBCR = float3x4(\n"
|
||||
"0.257, 0.504, 0.098, 16.0/255.0,\n"
|
||||
"-0.148, -0.291, 0.439, 128.0/255.0,\n"
|
||||
"0.439, -0.368, -0.071, 128.0/255.0\n"
|
||||
");\n"
|
||||
|
||||
"float3 SampleEFB(float2 coord)\n"
|
||||
"{\n"
|
||||
"float2 texCoord = lerp(float2(Params.TexLeft,Params.TexTop), "
|
||||
"float2(Params.TexRight,Params.TexBottom), coord / float2(Params.Width,Params.Height));\n"
|
||||
"return EFBTexture.Sample(EFBSampler, float3(texCoord, 0.0)).rgb;\n"
|
||||
"}\n"
|
||||
|
||||
"void main(out float4 ocol0 : SV_Target, in float4 Pos : SV_Position, in float2 Coord : "
|
||||
"ENCODECOORD)\n"
|
||||
"{\n"
|
||||
// Multiplying X by 2, moves pixel centers from (x+0.5) to (2x+1) instead of (2x+0.5), so
|
||||
// subtract 0.5 to compensate
|
||||
"float2 baseCoord = Coord * float2(2,1) - float2(0.5,0);\n"
|
||||
// FIXME: Shall we apply gamma here, or apply it below to the Y components?
|
||||
// Be careful if you apply it to Y! The Y components are in the range (16..235) / 255.
|
||||
"float3 sampleL = pow(abs(SampleEFB(baseCoord+float2(-1,0))), Params.Gamma);\n" // Left
|
||||
"float3 sampleM = pow(abs(SampleEFB(baseCoord)), Params.Gamma);\n" // Middle
|
||||
"float3 sampleR = pow(abs(SampleEFB(baseCoord+float2(1,0))), Params.Gamma);\n" // Right
|
||||
|
||||
"float3 yuvL = mul(RGB_TO_YCBCR, float4(sampleL,1));\n"
|
||||
"float3 yuvM = mul(RGB_TO_YCBCR, float4(sampleM,1));\n"
|
||||
"float3 yuvR = mul(RGB_TO_YCBCR, float4(sampleR,1));\n"
|
||||
|
||||
// The Y components correspond to two EFB pixels, while the U and V are
|
||||
// made from a blend of three EFB pixels.
|
||||
"float y0 = yuvM.r;\n"
|
||||
"float y1 = yuvR.r;\n"
|
||||
"float u0 = 0.25*yuvL.g + 0.5*yuvM.g + 0.25*yuvR.g;\n"
|
||||
"float v0 = 0.25*yuvL.b + 0.5*yuvM.b + 0.25*yuvR.b;\n"
|
||||
|
||||
"ocol0 = float4(y0, u0, y1, v0);\n"
|
||||
"}\n";
|
||||
|
||||
static const D3D11_INPUT_ELEMENT_DESC QUAD_LAYOUT_DESC[] = {
|
||||
{"POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0}};
|
||||
|
||||
static const struct QuadVertex
|
||||
{
|
||||
float posX;
|
||||
float posY;
|
||||
} QUAD_VERTS[4] = {{0, 0}, {1, 0}, {0, 1}, {1, 1}};
|
||||
|
||||
XFBEncoder::XFBEncoder()
|
||||
: m_out(nullptr), m_outRTV(nullptr), m_outStage(nullptr), m_encodeParams(nullptr),
|
||||
m_quad(nullptr), m_vShader(nullptr), m_quadLayout(nullptr), m_pShader(nullptr),
|
||||
m_xfbEncodeBlendState(nullptr), m_xfbEncodeDepthState(nullptr), m_xfbEncodeRastState(nullptr),
|
||||
m_efbSampler(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void XFBEncoder::Init()
|
||||
{
|
||||
HRESULT hr;
|
||||
|
||||
// Create output texture
|
||||
|
||||
// The pixel shader can generate one YUYV entry per pixel. One YUYV entry
|
||||
// is created for every two EFB pixels.
|
||||
D3D11_TEXTURE2D_DESC t2dd = CD3D11_TEXTURE2D_DESC(DXGI_FORMAT_R8G8B8A8_UNORM, MAX_XFB_WIDTH / 2,
|
||||
MAX_XFB_HEIGHT, 1, 1, D3D11_BIND_RENDER_TARGET);
|
||||
hr = D3D::device->CreateTexture2D(&t2dd, nullptr, &m_out);
|
||||
CHECK(SUCCEEDED(hr), "create xfb encoder output texture");
|
||||
D3D::SetDebugObjectName(m_out, "xfb 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_R8G8B8A8_UNORM);
|
||||
hr = D3D::device->CreateRenderTargetView(m_out, &rtvd, &m_outRTV);
|
||||
CHECK(SUCCEEDED(hr), "create xfb encoder output texture rtv");
|
||||
D3D::SetDebugObjectName(m_outRTV, "xfb 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 xfb encoder output staging buffer");
|
||||
D3D::SetDebugObjectName(m_outStage, "xfb encoder output staging buffer");
|
||||
|
||||
// Create constant buffer for uploading params to shaders
|
||||
|
||||
D3D11_BUFFER_DESC bd = CD3D11_BUFFER_DESC(sizeof(XFBEncodeParams), D3D11_BIND_CONSTANT_BUFFER);
|
||||
hr = D3D::device->CreateBuffer(&bd, nullptr, &m_encodeParams);
|
||||
CHECK(SUCCEEDED(hr), "create xfb encode params buffer");
|
||||
D3D::SetDebugObjectName(m_encodeParams, "xfb encoder params buffer");
|
||||
|
||||
// Create vertex quad
|
||||
|
||||
bd = CD3D11_BUFFER_DESC(sizeof(QUAD_VERTS), D3D11_BIND_VERTEX_BUFFER, D3D11_USAGE_IMMUTABLE);
|
||||
D3D11_SUBRESOURCE_DATA srd = {QUAD_VERTS, 0, 0};
|
||||
|
||||
hr = D3D::device->CreateBuffer(&bd, &srd, &m_quad);
|
||||
CHECK(SUCCEEDED(hr), "create xfb encode quad vertex buffer");
|
||||
D3D::SetDebugObjectName(m_quad, "xfb encoder quad vertex buffer");
|
||||
|
||||
// Create vertex shader
|
||||
|
||||
D3DBlob* bytecode = nullptr;
|
||||
if (!D3D::CompileVertexShader(XFB_ENCODE_VS, &bytecode))
|
||||
{
|
||||
ERROR_LOG(VIDEO, "XFB encode vertex shader failed to compile");
|
||||
return;
|
||||
}
|
||||
|
||||
hr = D3D::device->CreateVertexShader(bytecode->Data(), bytecode->Size(), nullptr, &m_vShader);
|
||||
CHECK(SUCCEEDED(hr), "create xfb encode vertex shader");
|
||||
D3D::SetDebugObjectName(m_vShader, "xfb encoder vertex shader");
|
||||
|
||||
// Create input layout for vertex quad using bytecode from vertex shader
|
||||
|
||||
hr = D3D::device->CreateInputLayout(QUAD_LAYOUT_DESC,
|
||||
sizeof(QUAD_LAYOUT_DESC) / sizeof(D3D11_INPUT_ELEMENT_DESC),
|
||||
bytecode->Data(), bytecode->Size(), &m_quadLayout);
|
||||
CHECK(SUCCEEDED(hr), "create xfb encode quad vertex layout");
|
||||
D3D::SetDebugObjectName(m_quadLayout, "xfb encoder quad layout");
|
||||
|
||||
bytecode->Release();
|
||||
|
||||
// Create pixel shader
|
||||
|
||||
m_pShader = D3D::CompileAndCreatePixelShader(XFB_ENCODE_PS);
|
||||
if (!m_pShader)
|
||||
{
|
||||
ERROR_LOG(VIDEO, "XFB encode pixel shader failed to compile");
|
||||
return;
|
||||
}
|
||||
D3D::SetDebugObjectName(m_pShader, "xfb encoder pixel shader");
|
||||
|
||||
// Create blend state
|
||||
|
||||
D3D11_BLEND_DESC bld = CD3D11_BLEND_DESC(CD3D11_DEFAULT());
|
||||
hr = D3D::device->CreateBlendState(&bld, &m_xfbEncodeBlendState);
|
||||
CHECK(SUCCEEDED(hr), "create xfb encode blend state");
|
||||
D3D::SetDebugObjectName(m_xfbEncodeBlendState, "xfb encoder blend state");
|
||||
|
||||
// Create depth state
|
||||
|
||||
D3D11_DEPTH_STENCIL_DESC dsd = CD3D11_DEPTH_STENCIL_DESC(CD3D11_DEFAULT());
|
||||
dsd.DepthEnable = FALSE;
|
||||
hr = D3D::device->CreateDepthStencilState(&dsd, &m_xfbEncodeDepthState);
|
||||
CHECK(SUCCEEDED(hr), "create xfb encode depth state");
|
||||
D3D::SetDebugObjectName(m_xfbEncodeDepthState, "xfb encoder depth state");
|
||||
|
||||
// Create rasterizer state
|
||||
|
||||
D3D11_RASTERIZER_DESC rd = CD3D11_RASTERIZER_DESC(CD3D11_DEFAULT());
|
||||
rd.CullMode = D3D11_CULL_NONE;
|
||||
rd.DepthClipEnable = FALSE;
|
||||
hr = D3D::device->CreateRasterizerState(&rd, &m_xfbEncodeRastState);
|
||||
CHECK(SUCCEEDED(hr), "create xfb encode rasterizer state");
|
||||
D3D::SetDebugObjectName(m_xfbEncodeRastState, "xfb encoder rast state");
|
||||
|
||||
// Create EFB texture sampler
|
||||
|
||||
D3D11_SAMPLER_DESC sd = CD3D11_SAMPLER_DESC(CD3D11_DEFAULT());
|
||||
sd.Filter = D3D11_FILTER_MIN_MAG_LINEAR_MIP_POINT;
|
||||
hr = D3D::device->CreateSamplerState(&sd, &m_efbSampler);
|
||||
CHECK(SUCCEEDED(hr), "create xfb encode texture sampler");
|
||||
D3D::SetDebugObjectName(m_efbSampler, "xfb encoder texture sampler");
|
||||
}
|
||||
|
||||
void XFBEncoder::Shutdown()
|
||||
{
|
||||
SAFE_RELEASE(m_efbSampler);
|
||||
SAFE_RELEASE(m_xfbEncodeRastState);
|
||||
SAFE_RELEASE(m_xfbEncodeDepthState);
|
||||
SAFE_RELEASE(m_xfbEncodeBlendState);
|
||||
SAFE_RELEASE(m_pShader);
|
||||
SAFE_RELEASE(m_quadLayout);
|
||||
SAFE_RELEASE(m_vShader);
|
||||
SAFE_RELEASE(m_quad);
|
||||
SAFE_RELEASE(m_encodeParams);
|
||||
SAFE_RELEASE(m_outStage);
|
||||
SAFE_RELEASE(m_outRTV);
|
||||
SAFE_RELEASE(m_out);
|
||||
}
|
||||
|
||||
void XFBEncoder::Encode(u8* dst, u32 width, u32 height, const EFBRectangle& srcRect, float gamma)
|
||||
{
|
||||
HRESULT hr;
|
||||
|
||||
// Reset API
|
||||
|
||||
g_renderer->ResetAPIState();
|
||||
|
||||
// Set up all the state for XFB encoding
|
||||
|
||||
D3D::stateman->SetPixelShader(m_pShader);
|
||||
D3D::stateman->SetVertexShader(m_vShader);
|
||||
D3D::stateman->SetGeometryShader(nullptr);
|
||||
|
||||
D3D::stateman->PushBlendState(m_xfbEncodeBlendState);
|
||||
D3D::stateman->PushDepthState(m_xfbEncodeDepthState);
|
||||
D3D::stateman->PushRasterizerState(m_xfbEncodeRastState);
|
||||
|
||||
D3D11_VIEWPORT vp = CD3D11_VIEWPORT(0.f, 0.f, FLOAT(width / 2), FLOAT(height));
|
||||
D3D::context->RSSetViewports(1, &vp);
|
||||
|
||||
D3D::stateman->SetInputLayout(m_quadLayout);
|
||||
D3D::stateman->SetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
|
||||
UINT stride = sizeof(QuadVertex);
|
||||
UINT offset = 0;
|
||||
D3D::stateman->SetVertexBuffer(m_quad, stride, offset);
|
||||
|
||||
TargetRectangle targetRect = g_renderer->ConvertEFBRectangle(srcRect);
|
||||
|
||||
XFBEncodeParams params = {0};
|
||||
params.Width = FLOAT(width);
|
||||
params.Height = FLOAT(height);
|
||||
params.TexLeft = FLOAT(targetRect.left) / g_renderer->GetTargetWidth();
|
||||
params.TexTop = FLOAT(targetRect.top) / g_renderer->GetTargetHeight();
|
||||
params.TexRight = FLOAT(targetRect.right) / g_renderer->GetTargetWidth();
|
||||
params.TexBottom = FLOAT(targetRect.bottom) / g_renderer->GetTargetHeight();
|
||||
params.Gamma = gamma;
|
||||
D3D::context->UpdateSubresource(m_encodeParams, 0, nullptr, ¶ms, 0, 0);
|
||||
|
||||
D3D::context->OMSetRenderTargets(1, &m_outRTV, nullptr);
|
||||
|
||||
ID3D11ShaderResourceView* pEFB = FramebufferManager::GetResolvedEFBColorTexture()->GetSRV();
|
||||
|
||||
D3D::stateman->SetVertexConstants(m_encodeParams);
|
||||
D3D::stateman->SetPixelConstants(m_encodeParams);
|
||||
D3D::stateman->SetTexture(0, pEFB);
|
||||
D3D::stateman->SetSampler(0, m_efbSampler);
|
||||
|
||||
// Encode!
|
||||
|
||||
D3D::stateman->Apply();
|
||||
D3D::context->Draw(4, 0);
|
||||
|
||||
// Copy to staging buffer
|
||||
|
||||
D3D11_BOX srcBox = CD3D11_BOX(0, 0, 0, width / 2, height, 1);
|
||||
D3D::context->CopySubresourceRegion(m_outStage, 0, 0, 0, 0, m_out, 0, &srcBox);
|
||||
|
||||
// Clean up state
|
||||
|
||||
D3D::context->OMSetRenderTargets(0, nullptr, nullptr);
|
||||
|
||||
D3D::stateman->SetSampler(0, nullptr);
|
||||
D3D::stateman->SetTexture(0, nullptr);
|
||||
D3D::stateman->SetPixelConstants(nullptr);
|
||||
D3D::stateman->SetVertexConstants(nullptr);
|
||||
|
||||
D3D::stateman->SetPixelShader(nullptr);
|
||||
D3D::stateman->SetVertexShader(nullptr);
|
||||
|
||||
D3D::stateman->PopRasterizerState();
|
||||
D3D::stateman->PopDepthState();
|
||||
D3D::stateman->PopBlendState();
|
||||
|
||||
// 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");
|
||||
|
||||
u8* src = (u8*)map.pData;
|
||||
for (unsigned int y = 0; y < height; ++y)
|
||||
{
|
||||
memcpy(dst, src, 2 * width);
|
||||
dst += bpmem.copyMipMapStrideChannels * 32;
|
||||
src += map.RowPitch;
|
||||
}
|
||||
|
||||
D3D::context->Unmap(m_outStage, 0);
|
||||
|
||||
// Restore API
|
||||
g_renderer->RestoreAPIState();
|
||||
D3D::stateman->Apply(); // force unbind efb texture as shader resource
|
||||
FramebufferManager::BindEFBRenderTarget();
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
// Copyright 2011 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "VideoCommon/VideoCommon.h"
|
||||
|
||||
struct ID3D11Texture2D;
|
||||
struct ID3D11RenderTargetView;
|
||||
struct ID3D11Buffer;
|
||||
struct ID3D11VertexShader;
|
||||
struct ID3D11PixelShader;
|
||||
struct ID3D11InputLayout;
|
||||
struct ID3D11BlendState;
|
||||
struct ID3D11DepthStencilState;
|
||||
struct ID3D11RasterizerState;
|
||||
struct ID3D11SamplerState;
|
||||
|
||||
namespace DX11
|
||||
{
|
||||
class XFBEncoder
|
||||
{
|
||||
public:
|
||||
XFBEncoder();
|
||||
|
||||
void Init();
|
||||
void Shutdown();
|
||||
|
||||
void Encode(u8* dst, u32 width, u32 height, const EFBRectangle& srcRect, float gamma);
|
||||
|
||||
private:
|
||||
ID3D11Texture2D* m_out;
|
||||
ID3D11RenderTargetView* m_outRTV;
|
||||
ID3D11Texture2D* m_outStage;
|
||||
ID3D11Buffer* m_encodeParams;
|
||||
ID3D11Buffer* m_quad;
|
||||
ID3D11VertexShader* m_vShader;
|
||||
ID3D11InputLayout* m_quadLayout;
|
||||
ID3D11PixelShader* m_pShader;
|
||||
ID3D11BlendState* m_xfbEncodeBlendState;
|
||||
ID3D11DepthStencilState* m_xfbEncodeDepthState;
|
||||
ID3D11RasterizerState* m_xfbEncodeRastState;
|
||||
ID3D11SamplerState* m_efbSampler;
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
// Copyright 2015 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "VideoCommon/FramebufferManagerBase.h"
|
||||
|
||||
class XFBSource : public XFBSourceBase
|
||||
{
|
||||
public:
|
||||
void DecodeToTexture(u32 xfb_addr, u32 fb_width, u32 fb_height) override {}
|
||||
void CopyEFB(float gamma) override {}
|
||||
};
|
||||
|
||||
class FramebufferManager : public FramebufferManagerBase
|
||||
{
|
||||
public:
|
||||
std::unique_ptr<XFBSourceBase> CreateXFBSource(unsigned int target_width,
|
||||
unsigned int target_height,
|
||||
unsigned int layers) override
|
||||
{
|
||||
return std::make_unique<XFBSource>();
|
||||
}
|
||||
|
||||
std::pair<u32, u32> GetTargetSize() const override { return std::make_pair(0, 0); }
|
||||
void CopyToRealXFB(u32 xfb_addr, u32 fb_stride, u32 fb_height, const EFBRectangle& source_rc,
|
||||
float gamma = 1.0f) override
|
||||
{
|
||||
}
|
||||
};
|
|
@ -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,7 +7,6 @@
|
|||
// 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"
|
||||
|
@ -15,6 +14,7 @@
|
|||
#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 @@ public:
|
|||
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 @@ public:
|
|||
{
|
||||
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,13 +96,6 @@ private:
|
|||
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;
|
||||
|
@ -123,7 +103,6 @@ private:
|
|||
|
||||
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()
|
||||
{
|
||||
// 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 {};
|
||||
|
||||
return SaveTexture(filename, GL_TEXTURE_2D_ARRAY, m_texId, m_config.width, m_config.height,
|
||||
level);
|
||||
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());
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
// 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 @@ public:
|
|||
~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 @@ public:
|
|||
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 @@ public:
|
|||
|
||||
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 @@ private:
|
|||
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,76 +193,19 @@ 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);
|
||||
scale_by_half && !params.depth, params.y_scale);
|
||||
|
||||
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);
|
||||
|
||||
FramebufferManager::SetFramebuffer(0);
|
||||
|
||||
g_renderer->RestoreAPIState();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace OGL
|
||||
|
|
|
@ -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
|
||||
|
|
33
Source/Core/VideoBackends/Software/CopyRegion.h
Normal file
33
Source/Core/VideoBackends/Software/CopyRegion.h
Normal file
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,30 +12,8 @@
|
|||
#include "VideoCommon/BPMemory.h"
|
||||
#include "VideoCommon/Fifo.h"
|
||||
|
||||
static const float s_gammaLUT[] = {1.0f, 1.7f, 2.2f, 1.0f};
|
||||
|
||||
namespace EfbCopy
|
||||
{
|
||||
static void CopyToXfb(u32 xfbAddr, u32 fbWidth, u32 fbHeight, const EFBRectangle& sourceRc,
|
||||
float Gamma)
|
||||
{
|
||||
DEBUG_LOG(VIDEO, "xfbaddr: %x, fbwidth: %i, fbheight: %i, source: (%i, %i, %i, %i), Gamma %f",
|
||||
xfbAddr, fbWidth, fbHeight, sourceRc.top, sourceRc.left, sourceRc.bottom,
|
||||
sourceRc.right, Gamma);
|
||||
|
||||
EfbInterface::yuv422_packed* xfb_in_ram =
|
||||
(EfbInterface::yuv422_packed*)Memory::GetPointer(xfbAddr);
|
||||
|
||||
EfbInterface::CopyToXFB(xfb_in_ram, fbWidth, fbHeight, sourceRc, Gamma);
|
||||
}
|
||||
|
||||
static void CopyToRam()
|
||||
{
|
||||
u8* dest_ptr = Memory::GetPointer(bpmem.copyTexDest << 5);
|
||||
|
||||
TextureEncoder::Encode(dest_ptr);
|
||||
}
|
||||
|
||||
void ClearEfb()
|
||||
{
|
||||
u32 clearColor = (bpmem.clearcolorAR & 0xff) << 24 | bpmem.clearcolorGB << 8 |
|
||||
|
@ -55,43 +33,4 @@ void ClearEfb()
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CopyEfb()
|
||||
{
|
||||
EFBRectangle rc;
|
||||
rc.left = (int)bpmem.copyTexSrcXY.x;
|
||||
rc.top = (int)bpmem.copyTexSrcXY.y;
|
||||
|
||||
// flipper represents the widths internally as last pixel minus starting pixel, so
|
||||
// these are zero indexed.
|
||||
rc.right = rc.left + (int)bpmem.copyTexSrcWH.x + 1;
|
||||
rc.bottom = rc.top + (int)bpmem.copyTexSrcWH.y + 1;
|
||||
|
||||
if (bpmem.triggerEFBCopy.copy_to_xfb)
|
||||
{
|
||||
float yScale;
|
||||
if (bpmem.triggerEFBCopy.scale_invert)
|
||||
yScale = 256.0f / (float)bpmem.dispcopyyscale;
|
||||
else
|
||||
yScale = (float)bpmem.dispcopyyscale / 256.0f;
|
||||
|
||||
float xfbLines = ((bpmem.copyTexSrcWH.y + 1.0f) * yScale);
|
||||
|
||||
if (yScale != 1.0)
|
||||
WARN_LOG(VIDEO, "yScale of %f is currently unsupported", yScale);
|
||||
|
||||
if ((u32)xfbLines > MAX_XFB_HEIGHT)
|
||||
{
|
||||
INFO_LOG(VIDEO, "Tried to scale EFB to too many XFB lines (%f)", xfbLines);
|
||||
xfbLines = MAX_XFB_HEIGHT;
|
||||
}
|
||||
|
||||
CopyToXfb(bpmem.copyTexDest << 5, bpmem.copyMipMapStrideChannels << 4, (u32)xfbLines, rc,
|
||||
s_gammaLUT[bpmem.triggerEFBCopy.gamma]);
|
||||
}
|
||||
else
|
||||
{
|
||||
CopyToRam(); // FIXME: should use the rectangle we have already created above
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,5 @@
|
|||
|
||||
namespace EfbCopy
|
||||
{
|
||||
// Copy the EFB to RAM as a texture format or XFB
|
||||
void CopyEfb();
|
||||
|
||||
void ClearEfb();
|
||||
}
|
||||
|
|
|
@ -7,11 +7,13 @@
|
|||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/Swap.h"
|
||||
|
||||
#include "VideoBackends/Software/CopyRegion.h"
|
||||
#include "VideoCommon/BPMemory.h"
|
||||
#include "VideoCommon/LookUpTables.h"
|
||||
#include "VideoCommon/PerfQueryBase.h"
|
||||
|
@ -495,19 +497,16 @@ u8* GetPixelPointer(u16 x, u16 y, bool depth)
|
|||
return &efb[GetColorOffset(x, y)];
|
||||
}
|
||||
|
||||
void CopyToXFB(yuv422_packed* xfb_in_ram, u32 fbWidth, u32 fbHeight, const EFBRectangle& sourceRc,
|
||||
float Gamma)
|
||||
void EncodeXFB(u8* xfb_in_ram, u32 memory_stride, const EFBRectangle& source_rect, float y_scale)
|
||||
{
|
||||
// FIXME: We should do Gamma correction
|
||||
|
||||
if (!xfb_in_ram)
|
||||
{
|
||||
WARN_LOG(VIDEO, "Tried to copy to invalid XFB address");
|
||||
return;
|
||||
}
|
||||
|
||||
int left = sourceRc.left;
|
||||
int right = sourceRc.right;
|
||||
int left = source_rect.left;
|
||||
int right = source_rect.right;
|
||||
|
||||
// this assumes copies will always start on an even (YU) pixel and the
|
||||
// copy always has an even width, which might not be true.
|
||||
|
@ -520,16 +519,11 @@ void CopyToXFB(yuv422_packed* xfb_in_ram, u32 fbWidth, u32 fbHeight, const EFBRe
|
|||
// Scanline buffer, leave room for borders
|
||||
yuv444 scanline[EFB_WIDTH + 2];
|
||||
|
||||
// our internal yuv444 type is not normalized, so black is {0, 0, 0} instead of {16, 128, 128}
|
||||
yuv444 black;
|
||||
black.Y = 0;
|
||||
black.U = 0;
|
||||
black.V = 0;
|
||||
static std::vector<yuv422_packed> source;
|
||||
source.resize(EFB_WIDTH * EFB_HEIGHT);
|
||||
yuv422_packed* src_ptr = &source[0];
|
||||
|
||||
scanline[0] = black; // black border at start
|
||||
scanline[right + 1] = black; // black border at end
|
||||
|
||||
for (u16 y = sourceRc.top; y < sourceRc.bottom; y++)
|
||||
for (float y = source_rect.top; y < source_rect.bottom; y++)
|
||||
{
|
||||
// Get a scanline of YUV pixels in 4:4:4 format
|
||||
|
||||
|
@ -538,50 +532,38 @@ void CopyToXFB(yuv422_packed* xfb_in_ram, u32 fbWidth, u32 fbHeight, const EFBRe
|
|||
scanline[i] = GetColorYUV(x, y);
|
||||
}
|
||||
|
||||
// Flipper clamps the border colors
|
||||
scanline[0] = scanline[1];
|
||||
scanline[right + 1] = scanline[right];
|
||||
|
||||
// And Downsample them to 4:2:2
|
||||
for (int i = 1, x = left; x < right; i += 2, x += 2)
|
||||
{
|
||||
// YU pixel
|
||||
xfb_in_ram[x].Y = scanline[i].Y + 16;
|
||||
src_ptr[x].Y = scanline[i].Y + 16;
|
||||
// we mix our color differences in 10 bit space so it will round more accurately
|
||||
// U[i] = 1/4 * U[i-1] + 1/2 * U[i] + 1/4 * U[i+1]
|
||||
xfb_in_ram[x].UV =
|
||||
128 + ((scanline[i - 1].U + (scanline[i].U << 1) + scanline[i + 1].U) >> 2);
|
||||
src_ptr[x].UV = 128 + ((scanline[i - 1].U + (scanline[i].U << 1) + scanline[i + 1].U) >> 2);
|
||||
|
||||
// YV pixel
|
||||
xfb_in_ram[x + 1].Y = scanline[i + 1].Y + 16;
|
||||
src_ptr[x + 1].Y = scanline[i + 1].Y + 16;
|
||||
// V[i] = 1/4 * V[i-1] + 1/2 * V[i] + 1/4 * V[i+1]
|
||||
xfb_in_ram[x + 1].UV =
|
||||
src_ptr[x + 1].UV =
|
||||
128 + ((scanline[i].V + (scanline[i + 1].V << 1) + scanline[i + 2].V) >> 2);
|
||||
}
|
||||
xfb_in_ram += fbWidth;
|
||||
}
|
||||
}
|
||||
|
||||
// Like CopyToXFB, but we copy directly into the OpenGL color texture without going via GameCube
|
||||
// main memory or doing a yuyv conversion
|
||||
void BypassXFB(u8* texture, u32 fbWidth, u32 fbHeight, const EFBRectangle& sourceRc, float Gamma)
|
||||
{
|
||||
if (fbWidth * fbHeight > MAX_XFB_WIDTH * MAX_XFB_HEIGHT)
|
||||
{
|
||||
ERROR_LOG(VIDEO, "Framebuffer is too large: %ix%i", fbWidth, fbHeight);
|
||||
return;
|
||||
src_ptr += memory_stride;
|
||||
}
|
||||
|
||||
size_t textureAddress = 0;
|
||||
const int left = sourceRc.left;
|
||||
const int right = sourceRc.right;
|
||||
auto dest_rect = EFBRectangle{source_rect.left, source_rect.top, source_rect.right,
|
||||
static_cast<int>(static_cast<float>(source_rect.bottom) * y_scale)};
|
||||
|
||||
for (u16 y = sourceRc.top; y < sourceRc.bottom; y++)
|
||||
{
|
||||
for (u16 x = left; x < right; x++)
|
||||
{
|
||||
const u32 color = Common::swap32(GetColor(x, y) | 0xFF);
|
||||
const std::size_t destination_size = dest_rect.GetWidth() * dest_rect.GetHeight() * 2;
|
||||
static std::vector<yuv422_packed> destination;
|
||||
destination.resize(dest_rect.GetWidth() * dest_rect.GetHeight());
|
||||
|
||||
std::memcpy(&texture[textureAddress], &color, sizeof(u32));
|
||||
textureAddress += sizeof(u32);
|
||||
}
|
||||
}
|
||||
SW::CopyRegion(source.data(), source_rect, destination.data(), dest_rect);
|
||||
|
||||
memcpy(xfb_in_ram, destination.data(), destination_size);
|
||||
}
|
||||
|
||||
bool ZCompare(u16 x, u16 y, u32 z)
|
||||
|
|
|
@ -57,9 +57,7 @@ u32 GetDepth(u16 x, u16 y);
|
|||
|
||||
u8* GetPixelPointer(u16 x, u16 y, bool depth);
|
||||
|
||||
void CopyToXFB(yuv422_packed* xfb_in_ram, u32 fbWidth, u32 fbHeight, const EFBRectangle& sourceRc,
|
||||
float Gamma);
|
||||
void BypassXFB(u8* texture, u32 fbWidth, u32 fbHeight, const EFBRectangle& sourceRc, float Gamma);
|
||||
void EncodeXFB(u8* xfb_in_ram, u32 memory_stride, const EFBRectangle& source_rect, float y_scale);
|
||||
|
||||
extern u32 perf_values[PQ_NUM_MEMBERS];
|
||||
inline void IncPerfCounterQuadCount(PerfQueryType type)
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "Common/Logging/Log.h"
|
||||
|
||||
#include "VideoBackends/Software/SWOGLWindow.h"
|
||||
#include "VideoBackends/Software/SWTexture.h"
|
||||
|
||||
std::unique_ptr<SWOGLWindow> SWOGLWindow::s_instance;
|
||||
|
||||
|
@ -53,9 +54,9 @@ void SWOGLWindow::Prepare()
|
|||
|
||||
std::string frag_shader = "in vec2 TexCoord;\n"
|
||||
"out vec4 ColorOut;\n"
|
||||
"uniform sampler2D Texture;\n"
|
||||
"uniform sampler2D samp;\n"
|
||||
"void main() {\n"
|
||||
" ColorOut = texture(Texture, TexCoord);\n"
|
||||
" ColorOut = texture(samp, TexCoord);\n"
|
||||
"}\n";
|
||||
|
||||
std::string vertex_shader = "out vec2 TexCoord;\n"
|
||||
|
@ -74,10 +75,10 @@ void SWOGLWindow::Prepare()
|
|||
|
||||
glUseProgram(m_image_program);
|
||||
|
||||
glUniform1i(glGetUniformLocation(m_image_program, "Texture"), 0);
|
||||
|
||||
glUniform1i(glGetUniformLocation(m_image_program, "samp"), 0);
|
||||
glGenTextures(1, &m_image_texture);
|
||||
glBindTexture(GL_TEXTURE_2D, m_image_texture);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
|
||||
|
@ -89,23 +90,27 @@ void SWOGLWindow::PrintText(const std::string& text, int x, int y, u32 color)
|
|||
m_text.push_back({text, x, y, color});
|
||||
}
|
||||
|
||||
void SWOGLWindow::ShowImage(const u8* data, int stride, int width, int height, float aspect)
|
||||
void SWOGLWindow::ShowImage(AbstractTexture* image, const EFBRectangle& xfb_region)
|
||||
{
|
||||
GLInterface->MakeCurrent();
|
||||
GLInterface->Update();
|
||||
Prepare();
|
||||
SW::SWTexture* sw_image = static_cast<SW::SWTexture*>(image);
|
||||
GLInterface->Update(); // just updates the render window position and the backbuffer size
|
||||
|
||||
GLsizei glWidth = (GLsizei)GLInterface->GetBackBufferWidth();
|
||||
GLsizei glHeight = (GLsizei)GLInterface->GetBackBufferHeight();
|
||||
|
||||
glViewport(0, 0, glWidth, glHeight);
|
||||
|
||||
glActiveTexture(GL_TEXTURE9);
|
||||
glBindTexture(GL_TEXTURE_2D, m_image_texture);
|
||||
|
||||
// TODO: Apply xfb_region
|
||||
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // 4-byte pixel alignment
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, stride / 4);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)width, (GLsizei)height, 0, GL_RGBA,
|
||||
GL_UNSIGNED_BYTE, data);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, sw_image->GetConfig().width);
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, static_cast<GLsizei>(sw_image->GetConfig().width),
|
||||
static_cast<GLsizei>(sw_image->GetConfig().height), 0, GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
sw_image->GetData());
|
||||
|
||||
glUseProgram(m_image_program);
|
||||
|
||||
|
@ -119,7 +124,6 @@ void SWOGLWindow::ShowImage(const u8* data, int stride, int width, int height, f
|
|||
m_text.clear();
|
||||
|
||||
GLInterface->Swap();
|
||||
GLInterface->ClearCurrent();
|
||||
}
|
||||
|
||||
int SWOGLWindow::PeekMessages()
|
||||
|
|
|
@ -9,18 +9,22 @@
|
|||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "VideoCommon/VideoCommon.h"
|
||||
|
||||
class AbstractTexture;
|
||||
|
||||
class SWOGLWindow
|
||||
{
|
||||
public:
|
||||
static void Init(void* window_handle);
|
||||
static void Shutdown();
|
||||
void Prepare();
|
||||
|
||||
// Will be printed on the *next* image
|
||||
void PrintText(const std::string& text, int x, int y, u32 color);
|
||||
|
||||
// Image to show, will be swapped immediately
|
||||
void ShowImage(const u8* data, int stride, int width, int height, float aspect);
|
||||
void ShowImage(AbstractTexture* image, const EFBRectangle& xfb_region);
|
||||
|
||||
int PeekMessages();
|
||||
|
||||
|
@ -28,8 +32,6 @@ public:
|
|||
|
||||
private:
|
||||
SWOGLWindow() {}
|
||||
void Prepare();
|
||||
|
||||
struct TextData
|
||||
{
|
||||
std::string text;
|
||||
|
|
|
@ -23,26 +23,13 @@
|
|||
#include "VideoCommon/VideoBackendBase.h"
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
|
||||
static u8* s_xfbColorTexture[2];
|
||||
static int s_currentColorTexture = 0;
|
||||
|
||||
SWRenderer::SWRenderer()
|
||||
: ::Renderer(static_cast<int>(MAX_XFB_WIDTH), static_cast<int>(MAX_XFB_HEIGHT))
|
||||
{
|
||||
}
|
||||
|
||||
SWRenderer::~SWRenderer()
|
||||
{
|
||||
delete[] s_xfbColorTexture[0];
|
||||
delete[] s_xfbColorTexture[1];
|
||||
}
|
||||
|
||||
void SWRenderer::Init()
|
||||
{
|
||||
s_xfbColorTexture[0] = new u8[MAX_XFB_WIDTH * MAX_XFB_HEIGHT * 4];
|
||||
s_xfbColorTexture[1] = new u8[MAX_XFB_WIDTH * MAX_XFB_HEIGHT * 4];
|
||||
|
||||
s_currentColorTexture = 0;
|
||||
}
|
||||
|
||||
void SWRenderer::Shutdown()
|
||||
|
@ -55,96 +42,17 @@ void SWRenderer::RenderText(const std::string& pstr, int left, int top, u32 colo
|
|||
SWOGLWindow::s_instance->PrintText(pstr, left, top, color);
|
||||
}
|
||||
|
||||
u8* SWRenderer::GetNextColorTexture()
|
||||
{
|
||||
return s_xfbColorTexture[!s_currentColorTexture];
|
||||
}
|
||||
|
||||
u8* SWRenderer::GetCurrentColorTexture()
|
||||
{
|
||||
return s_xfbColorTexture[s_currentColorTexture];
|
||||
}
|
||||
|
||||
void SWRenderer::SwapColorTexture()
|
||||
{
|
||||
s_currentColorTexture = !s_currentColorTexture;
|
||||
}
|
||||
|
||||
void SWRenderer::UpdateColorTexture(EfbInterface::yuv422_packed* xfb, u32 fbWidth, u32 fbHeight)
|
||||
{
|
||||
if (fbWidth * fbHeight > MAX_XFB_WIDTH * MAX_XFB_HEIGHT)
|
||||
{
|
||||
ERROR_LOG(VIDEO, "Framebuffer is too large: %ix%i", fbWidth, fbHeight);
|
||||
return;
|
||||
}
|
||||
|
||||
u32 offset = 0;
|
||||
u8* TexturePointer = GetNextColorTexture();
|
||||
|
||||
for (u16 y = 0; y < fbHeight; y++)
|
||||
{
|
||||
for (u16 x = 0; x < fbWidth; x += 2)
|
||||
{
|
||||
// We do this one color sample (aka 2 RGB pixles) at a time
|
||||
int Y1 = xfb[x].Y - 16;
|
||||
int Y2 = xfb[x + 1].Y - 16;
|
||||
int U = int(xfb[x].UV) - 128;
|
||||
int V = int(xfb[x + 1].UV) - 128;
|
||||
|
||||
// We do the inverse BT.601 conversion for YCbCr to RGB
|
||||
// http://www.equasys.de/colorconversion.html#YCbCr-RGBColorFormatConversion
|
||||
TexturePointer[offset++] = MathUtil::Clamp(int(1.164f * Y1 + 1.596f * V), 0, 255);
|
||||
TexturePointer[offset++] =
|
||||
MathUtil::Clamp(int(1.164f * Y1 - 0.392f * U - 0.813f * V), 0, 255);
|
||||
TexturePointer[offset++] = MathUtil::Clamp(int(1.164f * Y1 + 2.017f * U), 0, 255);
|
||||
TexturePointer[offset++] = 255;
|
||||
|
||||
TexturePointer[offset++] = MathUtil::Clamp(int(1.164f * Y2 + 1.596f * V), 0, 255);
|
||||
TexturePointer[offset++] =
|
||||
MathUtil::Clamp(int(1.164f * Y2 - 0.392f * U - 0.813f * V), 0, 255);
|
||||
TexturePointer[offset++] = MathUtil::Clamp(int(1.164f * Y2 + 2.017f * U), 0, 255);
|
||||
TexturePointer[offset++] = 255;
|
||||
}
|
||||
xfb += fbWidth;
|
||||
}
|
||||
SwapColorTexture();
|
||||
}
|
||||
|
||||
// Called on the GPU thread
|
||||
void SWRenderer::SwapImpl(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight,
|
||||
const EFBRectangle& rc, u64 ticks, float Gamma)
|
||||
void SWRenderer::SwapImpl(AbstractTexture* texture, const EFBRectangle& xfb_region, u64 ticks,
|
||||
float Gamma)
|
||||
{
|
||||
if (g_ActiveConfig.bUseXFB)
|
||||
{
|
||||
EfbInterface::yuv422_packed* xfb = (EfbInterface::yuv422_packed*)Memory::GetPointer(xfbAddr);
|
||||
UpdateColorTexture(xfb, fbWidth, fbHeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
EfbInterface::BypassXFB(GetCurrentColorTexture(), fbWidth, fbHeight, rc, Gamma);
|
||||
}
|
||||
|
||||
// Save screenshot
|
||||
if (IsFrameDumping())
|
||||
{
|
||||
AVIDump::Frame state = AVIDump::FetchState(ticks);
|
||||
DumpFrameData(GetCurrentColorTexture(), fbWidth, fbHeight, fbWidth * 4, state);
|
||||
FinishFrameData();
|
||||
}
|
||||
|
||||
OSD::DoCallbacks(OSD::CallbackType::OnFrame);
|
||||
|
||||
DrawDebugText();
|
||||
|
||||
SWOGLWindow::s_instance->ShowImage(GetCurrentColorTexture(), fbWidth * 4, fbWidth, fbHeight, 1.0);
|
||||
SWOGLWindow::s_instance->ShowImage(texture, xfb_region);
|
||||
|
||||
UpdateActiveConfig();
|
||||
|
||||
// virtual XFB is not supported
|
||||
if (g_ActiveConfig.bUseXFB)
|
||||
{
|
||||
Config::SetCurrent(Config::GFX_USE_REAL_XFB, true);
|
||||
}
|
||||
}
|
||||
|
||||
u32 SWRenderer::AccessEFB(EFBAccessType type, u32 x, u32 y, u32 InputData)
|
||||
|
|
|
@ -14,16 +14,10 @@ class SWRenderer : public Renderer
|
|||
{
|
||||
public:
|
||||
SWRenderer();
|
||||
~SWRenderer() override;
|
||||
|
||||
static void Init();
|
||||
static void Shutdown();
|
||||
|
||||
static u8* GetNextColorTexture();
|
||||
static u8* GetCurrentColorTexture();
|
||||
void SwapColorTexture();
|
||||
void UpdateColorTexture(EfbInterface::yuv422_packed* xfb, u32 fbWidth, u32 fbHeight);
|
||||
|
||||
void RenderText(const std::string& pstr, int left, int top, u32 color) override;
|
||||
u32 AccessEFB(EFBAccessType type, u32 x, u32 y, u32 poke_data) override;
|
||||
void PokeEFB(EFBAccessType type, const EfbPokeData* points, size_t num_points) override {}
|
||||
|
@ -32,8 +26,7 @@ public:
|
|||
|
||||
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;
|
||||
|
|
|
@ -4,10 +4,27 @@
|
|||
|
||||
#include "VideoBackends/Software/SWTexture.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "VideoBackends/Software/CopyRegion.h"
|
||||
|
||||
namespace SW
|
||||
{
|
||||
namespace
|
||||
{
|
||||
#pragma pack(push, 1)
|
||||
struct Pixel
|
||||
{
|
||||
u8 r;
|
||||
u8 g;
|
||||
u8 b;
|
||||
u8 a;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
}
|
||||
SWTexture::SWTexture(const TextureConfig& tex_config) : AbstractTexture(tex_config)
|
||||
{
|
||||
m_data.resize(tex_config.width * tex_config.height * 4);
|
||||
}
|
||||
|
||||
void SWTexture::Bind(unsigned int stage)
|
||||
|
@ -18,11 +35,47 @@ void SWTexture::CopyRectangleFromTexture(const AbstractTexture* source,
|
|||
const MathUtil::Rectangle<int>& srcrect,
|
||||
const MathUtil::Rectangle<int>& dstrect)
|
||||
{
|
||||
const SWTexture* software_source_texture = static_cast<const SWTexture*>(source);
|
||||
|
||||
if (srcrect.GetWidth() == dstrect.GetWidth() && srcrect.GetHeight() == dstrect.GetHeight())
|
||||
{
|
||||
m_data.assign(software_source_texture->GetData(),
|
||||
software_source_texture->GetData() + m_data.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<Pixel> source_pixels;
|
||||
source_pixels.resize(srcrect.GetHeight() * srcrect.GetWidth() * 4);
|
||||
memcpy(source_pixels.data(), software_source_texture->GetData(), source_pixels.size());
|
||||
|
||||
std::vector<Pixel> destination_pixels;
|
||||
destination_pixels.resize(dstrect.GetHeight() * dstrect.GetWidth() * 4);
|
||||
|
||||
CopyRegion(source_pixels.data(), srcrect, destination_pixels.data(), dstrect);
|
||||
memcpy(GetData(), destination_pixels.data(), destination_pixels.size());
|
||||
}
|
||||
}
|
||||
|
||||
void SWTexture::Load(u32 level, u32 width, u32 height, u32 row_length, const u8* buffer,
|
||||
size_t buffer_size)
|
||||
{
|
||||
m_data.assign(buffer, buffer + buffer_size);
|
||||
}
|
||||
|
||||
const u8* SWTexture::GetData() const
|
||||
{
|
||||
return m_data.data();
|
||||
}
|
||||
|
||||
u8* SWTexture::GetData()
|
||||
{
|
||||
return m_data.data();
|
||||
}
|
||||
|
||||
std::optional<AbstractTexture::RawTextureInfo> SWTexture::MapFullImpl()
|
||||
{
|
||||
return AbstractTexture::RawTextureInfo{GetData(), m_config.width * 4, m_config.width,
|
||||
m_config.height};
|
||||
}
|
||||
|
||||
} // namespace SW
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
#include "VideoCommon/AbstractTexture.h"
|
||||
|
@ -23,6 +25,14 @@ public:
|
|||
const MathUtil::Rectangle<int>& dstrect) override;
|
||||
void Load(u32 level, u32 width, u32 height, u32 row_length, const u8* buffer,
|
||||
size_t buffer_size) override;
|
||||
|
||||
const u8* GetData() const;
|
||||
u8* GetData();
|
||||
|
||||
private:
|
||||
std::optional<RawTextureInfo> MapFullImpl() override;
|
||||
|
||||
std::vector<u8> m_data;
|
||||
};
|
||||
|
||||
} // namespace SW
|
||||
|
|
|
@ -8,16 +8,17 @@
|
|||
#include <utility>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/GL/GLInterfaceBase.h"
|
||||
|
||||
#include "VideoBackends/Software/Clipper.h"
|
||||
#include "VideoBackends/Software/DebugUtil.h"
|
||||
#include "VideoBackends/Software/EfbCopy.h"
|
||||
#include "VideoBackends/Software/EfbInterface.h"
|
||||
#include "VideoBackends/Software/Rasterizer.h"
|
||||
#include "VideoBackends/Software/SWOGLWindow.h"
|
||||
#include "VideoBackends/Software/SWRenderer.h"
|
||||
#include "VideoBackends/Software/SWTexture.h"
|
||||
#include "VideoBackends/Software/SWVertexLoader.h"
|
||||
#include "VideoBackends/Software/TextureCache.h"
|
||||
#include "VideoBackends/Software/VideoBackend.h"
|
||||
|
||||
#include "VideoCommon/FramebufferManagerBase.h"
|
||||
|
@ -46,58 +47,6 @@ public:
|
|||
bool IsFlushed() const override { return true; }
|
||||
};
|
||||
|
||||
class TextureCache : public TextureCacheBase
|
||||
{
|
||||
public:
|
||||
bool CompileShaders() override { return true; }
|
||||
void DeleteShaders() override {}
|
||||
void ConvertTexture(TCacheEntry* entry, TCacheEntry* unconverted, const void* palette,
|
||||
TLUTFormat format) override
|
||||
{
|
||||
}
|
||||
void CopyEFB(u8* dst, const EFBCopyParams& params, u32 native_width, u32 bytes_per_row,
|
||||
u32 num_blocks_y, u32 memory_stride, const EFBRectangle& src_rect,
|
||||
bool scale_by_half) override
|
||||
{
|
||||
EfbCopy::CopyEfb();
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<AbstractTexture> CreateTexture(const TextureConfig& config) override
|
||||
{
|
||||
return std::make_unique<SWTexture>(config);
|
||||
}
|
||||
|
||||
void CopyEFBToCacheEntry(TCacheEntry* entry, bool is_depth_copy, const EFBRectangle& src_rect,
|
||||
bool scale_by_half, unsigned int cbuf_id, const float* colmat) override
|
||||
{
|
||||
EfbCopy::CopyEfb();
|
||||
}
|
||||
};
|
||||
|
||||
class XFBSource : public XFBSourceBase
|
||||
{
|
||||
void DecodeToTexture(u32 xfbAddr, u32 fbWidth, u32 fbHeight) override {}
|
||||
void CopyEFB(float Gamma) override {}
|
||||
};
|
||||
|
||||
class FramebufferManager : public FramebufferManagerBase
|
||||
{
|
||||
std::unique_ptr<XFBSourceBase> CreateXFBSource(unsigned int target_width,
|
||||
unsigned int target_height,
|
||||
unsigned int layers) override
|
||||
{
|
||||
return std::make_unique<XFBSource>();
|
||||
}
|
||||
|
||||
std::pair<u32, u32> GetTargetSize() const override { return std::make_pair(0, 0); }
|
||||
void CopyToRealXFB(u32 xfbAddr, u32 fbStride, u32 fbHeight, const EFBRectangle& sourceRc,
|
||||
float Gamma = 1.0f) override
|
||||
{
|
||||
EfbCopy::CopyEfb();
|
||||
}
|
||||
};
|
||||
|
||||
std::string VideoSoftware::GetName() const
|
||||
{
|
||||
return "Software Renderer";
|
||||
|
@ -123,6 +72,8 @@ void VideoSoftware::InitBackendInfo()
|
|||
g_Config.backend_info.bSupportsGPUTextureDecoding = false;
|
||||
g_Config.backend_info.bSupportsST3CTextures = false;
|
||||
g_Config.backend_info.bSupportsBPTCTextures = false;
|
||||
g_Config.backend_info.bSupportsCopyToVram = false;
|
||||
g_Config.backend_info.bForceCopyToRam = true;
|
||||
|
||||
// aamodes
|
||||
g_Config.backend_info.AAModes = {1};
|
||||
|
@ -169,12 +120,14 @@ void VideoSoftware::Video_Cleanup()
|
|||
// This is called after Video_Initialize() from the Core
|
||||
void VideoSoftware::Video_Prepare()
|
||||
{
|
||||
GLInterface->MakeCurrent();
|
||||
SWOGLWindow::s_instance->Prepare();
|
||||
|
||||
g_renderer = std::make_unique<SWRenderer>();
|
||||
g_vertex_manager = std::make_unique<SWVertexLoader>();
|
||||
g_perf_query = std::make_unique<PerfQuery>();
|
||||
g_texture_cache = std::make_unique<TextureCache>();
|
||||
SWRenderer::Init();
|
||||
g_framebuffer_manager = std::make_unique<FramebufferManager>();
|
||||
}
|
||||
|
||||
unsigned int VideoSoftware::PeekMessages()
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Clipper.h" />
|
||||
<ClInclude Include="CopyRegion.h" />
|
||||
<ClInclude Include="DebugUtil.h" />
|
||||
<ClInclude Include="EfbCopy.h" />
|
||||
<ClInclude Include="EfbInterface.h" />
|
||||
|
@ -65,6 +66,7 @@
|
|||
<ClInclude Include="SWTexture.h" />
|
||||
<ClInclude Include="SWVertexLoader.h" />
|
||||
<ClInclude Include="Tev.h" />
|
||||
<ClInclude Include="TextureCache.h" />
|
||||
<ClInclude Include="TextureEncoder.h" />
|
||||
<ClInclude Include="TextureSampler.h" />
|
||||
<ClInclude Include="TransformUnit.h" />
|
||||
|
|
40
Source/Core/VideoBackends/Software/TextureCache.h
Normal file
40
Source/Core/VideoBackends/Software/TextureCache.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include "VideoBackends/Software/SWTexture.h"
|
||||
#include "VideoBackends/Software/TextureEncoder.h"
|
||||
#include "VideoCommon/TextureCacheBase.h"
|
||||
|
||||
namespace SW
|
||||
{
|
||||
class TextureCache : public TextureCacheBase
|
||||
{
|
||||
public:
|
||||
bool CompileShaders() override { return true; }
|
||||
void DeleteShaders() override {}
|
||||
void ConvertTexture(TCacheEntry* entry, TCacheEntry* unconverted, const void* palette,
|
||||
TLUTFormat format) override
|
||||
{
|
||||
}
|
||||
void CopyEFB(u8* dst, const EFBCopyParams& params, u32 native_width, u32 bytes_per_row,
|
||||
u32 num_blocks_y, u32 memory_stride, const EFBRectangle& src_rect,
|
||||
bool scale_by_half) override
|
||||
{
|
||||
TextureEncoder::Encode(dst, params, native_width, bytes_per_row, num_blocks_y, memory_stride,
|
||||
src_rect, scale_by_half);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<AbstractTexture> CreateTexture(const TextureConfig& config) override
|
||||
{
|
||||
return std::make_unique<SWTexture>(config);
|
||||
}
|
||||
|
||||
void CopyEFBToCacheEntry(TCacheEntry* entry, bool is_depth_copy, const EFBRectangle& src_rect,
|
||||
bool scale_by_half, unsigned int cbuf_id, const float* colmat) override
|
||||
{
|
||||
// TODO: If we ever want to "fake" vram textures, we would need to implement this
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace SW
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include "VideoCommon/BPMemory.h"
|
||||
#include "VideoCommon/LookUpTables.h"
|
||||
#include "VideoCommon/TextureCacheBase.h"
|
||||
#include "VideoCommon/TextureDecoder.h"
|
||||
|
||||
namespace TextureEncoder
|
||||
|
@ -1416,37 +1417,64 @@ static void EncodeZ24halfscale(u8* dst, const u8* src, EFBCopyFormat format)
|
|||
}
|
||||
}
|
||||
|
||||
void Encode(u8* dest_ptr)
|
||||
namespace
|
||||
{
|
||||
auto pixelformat = bpmem.zcontrol.pixel_format;
|
||||
bool bFromZBuffer = pixelformat == PEControl::Z24;
|
||||
bool bIsIntensityFmt = bpmem.triggerEFBCopy.intensity_fmt > 0;
|
||||
EFBCopyFormat copyfmt = bpmem.triggerEFBCopy.tp_realFormat();
|
||||
void EncodeEfbCopy(u8* dst, const EFBCopyParams& params, u32 native_width, u32 bytes_per_row,
|
||||
u32 num_blocks_y, u32 memory_stride, const EFBRectangle& src_rect,
|
||||
bool scale_by_half)
|
||||
{
|
||||
const u8* src = EfbInterface::GetPixelPointer(src_rect.left, src_rect.top, params.depth);
|
||||
|
||||
const u8* src =
|
||||
EfbInterface::GetPixelPointer(bpmem.copyTexSrcXY.x, bpmem.copyTexSrcXY.y, bFromZBuffer);
|
||||
|
||||
if (bpmem.triggerEFBCopy.half_scale)
|
||||
if (scale_by_half)
|
||||
{
|
||||
if (pixelformat == PEControl::RGBA6_Z24)
|
||||
EncodeRGBA6halfscale(dest_ptr, src, copyfmt, bIsIntensityFmt);
|
||||
else if (pixelformat == PEControl::RGB8_Z24)
|
||||
EncodeRGB8halfscale(dest_ptr, src, copyfmt, bIsIntensityFmt);
|
||||
else if (pixelformat == PEControl::RGB565_Z16) // not supported
|
||||
EncodeRGB8halfscale(dest_ptr, src, copyfmt, bIsIntensityFmt);
|
||||
else if (pixelformat == PEControl::Z24)
|
||||
EncodeZ24halfscale(dest_ptr, src, copyfmt);
|
||||
switch (params.efb_format)
|
||||
{
|
||||
case PEControl::RGBA6_Z24:
|
||||
EncodeRGBA6halfscale(dst, src, params.copy_format, params.yuv);
|
||||
break;
|
||||
case PEControl::RGB8_Z24:
|
||||
EncodeRGB8halfscale(dst, src, params.copy_format, params.yuv);
|
||||
break;
|
||||
case PEControl::RGB565_Z16:
|
||||
EncodeRGB8halfscale(dst, src, params.copy_format, params.yuv);
|
||||
break;
|
||||
case PEControl::Z24:
|
||||
EncodeZ24halfscale(dst, src, params.copy_format);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pixelformat == PEControl::RGBA6_Z24)
|
||||
EncodeRGBA6(dest_ptr, src, copyfmt, bIsIntensityFmt);
|
||||
else if (pixelformat == PEControl::RGB8_Z24)
|
||||
EncodeRGB8(dest_ptr, src, copyfmt, bIsIntensityFmt);
|
||||
else if (pixelformat == PEControl::RGB565_Z16) // not supported
|
||||
EncodeRGB8(dest_ptr, src, copyfmt, bIsIntensityFmt);
|
||||
else if (pixelformat == PEControl::Z24)
|
||||
EncodeZ24(dest_ptr, src, copyfmt);
|
||||
switch (params.efb_format)
|
||||
{
|
||||
case PEControl::RGBA6_Z24:
|
||||
EncodeRGBA6(dst, src, params.copy_format, params.yuv);
|
||||
break;
|
||||
case PEControl::RGB8_Z24:
|
||||
EncodeRGB8(dst, src, params.copy_format, params.yuv);
|
||||
break;
|
||||
case PEControl::RGB565_Z16:
|
||||
EncodeRGB8(dst, src, params.copy_format, params.yuv);
|
||||
break;
|
||||
case PEControl::Z24:
|
||||
EncodeZ24(dst, src, params.copy_format);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Encode(u8* dst, const EFBCopyParams& params, u32 native_width, u32 bytes_per_row,
|
||||
u32 num_blocks_y, u32 memory_stride, const EFBRectangle& src_rect, bool scale_by_half)
|
||||
{
|
||||
if (params.copy_format == EFBCopyFormat::XFB)
|
||||
{
|
||||
EfbInterface::EncodeXFB(dst, native_width, src_rect, params.y_scale);
|
||||
}
|
||||
else
|
||||
{
|
||||
EncodeEfbCopy(dst, params, native_width, bytes_per_row, num_blocks_y, memory_stride, src_rect,
|
||||
scale_by_half);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,12 @@
|
|||
#pragma once
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "VideoCommon/VideoCommon.h"
|
||||
|
||||
struct EFBCopyParams;
|
||||
|
||||
namespace TextureEncoder
|
||||
{
|
||||
void Encode(u8* dest_ptr);
|
||||
void Encode(u8* dst, const EFBCopyParams& params, u32 native_width, u32 bytes_per_row,
|
||||
u32 num_blocks_y, u32 memory_stride, const EFBRectangle& src_rect, bool scale_by_half);
|
||||
}
|
||||
|
|
|
@ -89,11 +89,6 @@ MultisamplingState FramebufferManager::GetEFBMultisamplingState() const
|
|||
return ms;
|
||||
}
|
||||
|
||||
std::pair<u32, u32> FramebufferManager::GetTargetSize() const
|
||||
{
|
||||
return std::make_pair(GetEFBWidth(), GetEFBHeight());
|
||||
}
|
||||
|
||||
bool FramebufferManager::Initialize()
|
||||
{
|
||||
if (!CreateEFBRenderPass())
|
||||
|
@ -1382,102 +1377,4 @@ void FramebufferManager::DestroyPokeShaders()
|
|||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<XFBSourceBase> FramebufferManager::CreateXFBSource(unsigned int target_width,
|
||||
unsigned int target_height,
|
||||
unsigned int layers)
|
||||
{
|
||||
TextureConfig config;
|
||||
config.width = target_width;
|
||||
config.height = target_height;
|
||||
config.layers = layers;
|
||||
config.rendertarget = true;
|
||||
auto texture = TextureCache::GetInstance()->CreateTexture(config);
|
||||
if (!texture)
|
||||
{
|
||||
PanicAlert("Failed to create texture for XFB source");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::make_unique<XFBSource>(std::move(texture));
|
||||
}
|
||||
|
||||
void FramebufferManager::CopyToRealXFB(u32 xfb_addr, u32 fb_stride, u32 fb_height,
|
||||
const EFBRectangle& source_rc, float gamma)
|
||||
{
|
||||
// Pending/batched EFB pokes should be included in the copied image.
|
||||
FlushEFBPokes();
|
||||
|
||||
// Schedule early command-buffer execution.
|
||||
StateTracker::GetInstance()->EndRenderPass();
|
||||
StateTracker::GetInstance()->OnReadback();
|
||||
|
||||
// GPU EFB textures -> Guest memory
|
||||
u8* xfb_ptr = Memory::GetPointer(xfb_addr);
|
||||
_assert_(xfb_ptr);
|
||||
|
||||
// source_rc is in native coordinates, so scale it to the internal resolution.
|
||||
TargetRectangle scaled_rc = g_renderer->ConvertEFBRectangle(source_rc);
|
||||
VkRect2D scaled_rc_vk = {
|
||||
{scaled_rc.left, scaled_rc.top},
|
||||
{static_cast<u32>(scaled_rc.GetWidth()), static_cast<u32>(scaled_rc.GetHeight())}};
|
||||
Texture2D* src_texture = ResolveEFBColorTexture(scaled_rc_vk);
|
||||
|
||||
// The destination stride can differ from the copy region width, in which case the pixels
|
||||
// outside the copy region should not be written to.
|
||||
TextureCache::GetInstance()->GetTextureConverter()->EncodeTextureToMemoryYUYV(
|
||||
xfb_ptr, static_cast<u32>(source_rc.GetWidth()), fb_stride, fb_height, src_texture,
|
||||
scaled_rc);
|
||||
|
||||
// If we sourced directly from the EFB framebuffer, restore it to a color attachment.
|
||||
if (src_texture == m_efb_color_texture.get())
|
||||
{
|
||||
src_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
||||
}
|
||||
}
|
||||
|
||||
XFBSource::XFBSource(std::unique_ptr<AbstractTexture> texture)
|
||||
: XFBSourceBase(), m_texture(std::move(texture))
|
||||
{
|
||||
}
|
||||
|
||||
XFBSource::~XFBSource()
|
||||
{
|
||||
}
|
||||
|
||||
VKTexture* XFBSource::GetTexture() const
|
||||
{
|
||||
return static_cast<VKTexture*>(m_texture.get());
|
||||
}
|
||||
|
||||
void XFBSource::DecodeToTexture(u32 xfb_addr, u32 fb_width, u32 fb_height)
|
||||
{
|
||||
// Guest memory -> GPU EFB Textures
|
||||
const u8* src_ptr = Memory::GetPointer(xfb_addr);
|
||||
_assert_(src_ptr);
|
||||
TextureCache::GetInstance()->GetTextureConverter()->DecodeYUYVTextureFromMemory(
|
||||
static_cast<VKTexture*>(m_texture.get()), src_ptr, fb_width, fb_width * 2, fb_height);
|
||||
}
|
||||
|
||||
void XFBSource::CopyEFB(float gamma)
|
||||
{
|
||||
// Pending/batched EFB pokes should be included in the copied image.
|
||||
FramebufferManager::GetInstance()->FlushEFBPokes();
|
||||
|
||||
// Virtual XFB, copy EFB at native resolution to m_texture
|
||||
MathUtil::Rectangle<int> rect(0, 0, static_cast<int>(texWidth), static_cast<int>(texHeight));
|
||||
VkRect2D vk_rect = {{rect.left, rect.top},
|
||||
{static_cast<u32>(rect.GetWidth()), static_cast<u32>(rect.GetHeight())}};
|
||||
|
||||
Texture2D* src_texture = FramebufferManager::GetInstance()->ResolveEFBColorTexture(vk_rect);
|
||||
static_cast<VKTexture*>(m_texture.get())->CopyRectangleFromTexture(src_texture, rect, rect);
|
||||
|
||||
// If we sourced directly from the EFB framebuffer, restore it to a color attachment.
|
||||
if (src_texture == FramebufferManager::GetInstance()->GetEFBColorTexture())
|
||||
{
|
||||
src_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
|
|
|
@ -43,15 +43,6 @@ public:
|
|||
u32 GetEFBLayers() const;
|
||||
VkSampleCountFlagBits GetEFBSamples() const;
|
||||
MultisamplingState GetEFBMultisamplingState() const;
|
||||
std::pair<u32, u32> GetTargetSize() const override;
|
||||
|
||||
std::unique_ptr<XFBSourceBase> CreateXFBSource(unsigned int target_width,
|
||||
unsigned int target_height,
|
||||
unsigned int layers) override;
|
||||
|
||||
// GPU EFB textures -> Guest
|
||||
void CopyToRealXFB(u32 xfb_addr, u32 fb_stride, u32 fb_height, const EFBRectangle& source_rc,
|
||||
float gamma = 1.0f) override;
|
||||
|
||||
void ResizeEFBTextures();
|
||||
|
||||
|
@ -169,23 +160,4 @@ private:
|
|||
VkShaderModule m_poke_fragment_shader = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
// The XFB source class simply wraps a texture cache entry.
|
||||
// All the required functionality is provided by TextureCache.
|
||||
class XFBSource final : public XFBSourceBase
|
||||
{
|
||||
public:
|
||||
explicit XFBSource(std::unique_ptr<AbstractTexture> texture);
|
||||
~XFBSource();
|
||||
|
||||
VKTexture* GetTexture() const;
|
||||
// Guest -> GPU EFB Textures
|
||||
void DecodeToTexture(u32 xfb_addr, u32 fb_width, u32 fb_height) override;
|
||||
|
||||
// Used for virtual XFB
|
||||
void CopyEFB(float gamma) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<AbstractTexture> m_texture;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
#include "VideoCommon/SamplerCommon.h"
|
||||
#include "VideoCommon/TextureCacheBase.h"
|
||||
#include "VideoCommon/VideoBackendBase.h"
|
||||
#include "VideoCommon/VideoCommon.h"
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
#include "VideoCommon/XFMemory.h"
|
||||
|
||||
|
@ -60,11 +61,6 @@ Renderer::~Renderer()
|
|||
{
|
||||
UpdateActiveConfig();
|
||||
|
||||
// Ensure all frames are written to frame dump at shutdown.
|
||||
if (m_frame_dumping_active)
|
||||
EndFrameDumping();
|
||||
|
||||
DestroyFrameDumpResources();
|
||||
DestroyShaders();
|
||||
DestroySemaphores();
|
||||
}
|
||||
|
@ -485,26 +481,13 @@ void Renderer::ReinterpretPixelData(unsigned int convtype)
|
|||
BindEFBToStateTracker();
|
||||
}
|
||||
|
||||
void Renderer::SwapImpl(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height,
|
||||
const EFBRectangle& rc, u64 ticks, float gamma)
|
||||
void Renderer::SwapImpl(AbstractTexture* texture, const EFBRectangle& xfb_region, u64 ticks,
|
||||
float Gamma)
|
||||
{
|
||||
// Pending/batched EFB pokes should be included in the final image.
|
||||
FramebufferManager::GetInstance()->FlushEFBPokes();
|
||||
|
||||
// Check that we actually have an image to render in XFB-on modes.
|
||||
if ((!m_xfb_written && !g_ActiveConfig.RealXFBEnabled()) || !fb_width || !fb_height)
|
||||
{
|
||||
Core::Callback_VideoCopiedToXFB(false);
|
||||
return;
|
||||
}
|
||||
u32 xfb_count = 0;
|
||||
const XFBSourceBase* const* xfb_sources =
|
||||
FramebufferManager::GetXFBSource(xfb_addr, fb_stride, fb_height, &xfb_count);
|
||||
if (g_ActiveConfig.VirtualXFBEnabled() && (!xfb_sources || xfb_count == 0))
|
||||
{
|
||||
Core::Callback_VideoCopiedToXFB(false);
|
||||
return;
|
||||
}
|
||||
auto* xfb_texture = static_cast<VKTexture*>(texture);
|
||||
|
||||
// End the current render pass.
|
||||
StateTracker::GetInstance()->EndRenderPass();
|
||||
|
@ -514,31 +497,6 @@ void Renderer::SwapImpl(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height
|
|||
// are determined by guest state. Currently, the only way to catch these is to update every frame.
|
||||
UpdateDrawRectangle();
|
||||
|
||||
// Scale the source rectangle to the internal resolution when XFB is disabled.
|
||||
TargetRectangle scaled_efb_rect = Renderer::ConvertEFBRectangle(rc);
|
||||
|
||||
// If MSAA is enabled, and we're not using XFB, we need to resolve the EFB framebuffer before
|
||||
// rendering the final image to the screen, or dumping the frame. This is because we can't resolve
|
||||
// an image within a render pass, which will have already started by the time it is used.
|
||||
TransitionBuffersForSwap(scaled_efb_rect, xfb_sources, xfb_count);
|
||||
|
||||
// Render the frame dump image if enabled.
|
||||
if (IsFrameDumping())
|
||||
{
|
||||
// If we haven't dumped a single frame yet, set up frame dumping.
|
||||
if (!m_frame_dumping_active)
|
||||
StartFrameDumping();
|
||||
|
||||
DrawFrameDump(scaled_efb_rect, xfb_addr, xfb_sources, xfb_count, fb_width, fb_stride, fb_height,
|
||||
ticks);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If frame dumping was previously enabled, flush all frames and remove the fence callback.
|
||||
if (m_frame_dumping_active)
|
||||
EndFrameDumping();
|
||||
}
|
||||
|
||||
// Ensure the worker thread is not still submitting a previous command buffer.
|
||||
// In other words, the last frame has been submitted (otherwise the next call would
|
||||
// be a race, as the image may not have been consumed yet).
|
||||
|
@ -547,7 +505,7 @@ void Renderer::SwapImpl(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height
|
|||
// Draw to the screen if we have a swap chain.
|
||||
if (m_swap_chain)
|
||||
{
|
||||
DrawScreen(scaled_efb_rect, xfb_addr, xfb_sources, xfb_count, fb_width, fb_stride, fb_height);
|
||||
DrawScreen(xfb_texture, xfb_region);
|
||||
|
||||
// Submit the current command buffer, signaling rendering finished semaphore when it's done
|
||||
// Because this final command buffer is rendering to the swap chain, we need to wait for
|
||||
|
@ -581,15 +539,12 @@ void Renderer::SwapImpl(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height
|
|||
// Handle host window resizes.
|
||||
CheckForSurfaceChange();
|
||||
|
||||
// Handle output size changes from the guest.
|
||||
// There is a downside to doing this here is that if the game changes its XFB source area,
|
||||
// the changes will be delayed by one frame. For the moment it has to be done here because
|
||||
// this can cause a target size change, which would result in a black frame if done earlier.
|
||||
CheckForTargetResize(fb_width, fb_stride, fb_height);
|
||||
if (CalculateTargetSize())
|
||||
ResizeEFBTextures();
|
||||
|
||||
// Update the window size based on the frame that was just rendered.
|
||||
// Due to depending on guest state, we need to call this every frame.
|
||||
SetWindowSize(static_cast<int>(fb_stride), static_cast<int>(fb_height));
|
||||
SetWindowSize(xfb_texture->GetConfig().width, xfb_texture->GetConfig().height);
|
||||
|
||||
// Clean up stale textures.
|
||||
TextureCache::GetInstance()->Cleanup(frameCount);
|
||||
|
@ -598,125 +553,7 @@ void Renderer::SwapImpl(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height
|
|||
g_shader_cache->RetrieveAsyncShaders();
|
||||
}
|
||||
|
||||
void Renderer::TransitionBuffersForSwap(const TargetRectangle& scaled_rect,
|
||||
const XFBSourceBase* const* xfb_sources, u32 xfb_count)
|
||||
{
|
||||
VkCommandBuffer command_buffer = g_command_buffer_mgr->GetCurrentCommandBuffer();
|
||||
|
||||
if (!g_ActiveConfig.bUseXFB)
|
||||
{
|
||||
// Drawing EFB direct.
|
||||
if (g_ActiveConfig.iMultisamples > 1)
|
||||
{
|
||||
// While the source rect can be out-of-range when drawing, the resolve rectangle must be
|
||||
// within the bounds of the texture.
|
||||
VkRect2D region = {
|
||||
{scaled_rect.left, scaled_rect.top},
|
||||
{static_cast<u32>(scaled_rect.GetWidth()), static_cast<u32>(scaled_rect.GetHeight())}};
|
||||
region = Util::ClampRect2D(region, FramebufferManager::GetInstance()->GetEFBWidth(),
|
||||
FramebufferManager::GetInstance()->GetEFBHeight());
|
||||
|
||||
Vulkan::Texture2D* rtex = FramebufferManager::GetInstance()->ResolveEFBColorTexture(region);
|
||||
rtex->TransitionToLayout(command_buffer, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
}
|
||||
else
|
||||
{
|
||||
FramebufferManager::GetInstance()->GetEFBColorTexture()->TransitionToLayout(
|
||||
command_buffer, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Drawing XFB sources, so transition all of them.
|
||||
// Don't need the EFB, so leave it as-is.
|
||||
for (u32 i = 0; i < xfb_count; i++)
|
||||
{
|
||||
const XFBSource* xfb_source = static_cast<const XFBSource*>(xfb_sources[i]);
|
||||
xfb_source->GetTexture()->GetRawTexIdentifier()->TransitionToLayout(
|
||||
command_buffer, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::DrawFrame(VkRenderPass render_pass, const TargetRectangle& target_rect,
|
||||
const TargetRectangle& scaled_efb_rect, u32 xfb_addr,
|
||||
const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width,
|
||||
u32 fb_stride, u32 fb_height)
|
||||
{
|
||||
if (!g_ActiveConfig.bUseXFB)
|
||||
DrawEFB(render_pass, target_rect, scaled_efb_rect);
|
||||
else if (!g_ActiveConfig.bUseRealXFB)
|
||||
DrawVirtualXFB(render_pass, target_rect, xfb_addr, xfb_sources, xfb_count, fb_width, fb_stride,
|
||||
fb_height);
|
||||
else
|
||||
DrawRealXFB(render_pass, target_rect, xfb_sources, xfb_count, fb_width, fb_stride, fb_height);
|
||||
}
|
||||
|
||||
void Renderer::DrawEFB(VkRenderPass render_pass, const TargetRectangle& target_rect,
|
||||
const TargetRectangle& scaled_efb_rect)
|
||||
{
|
||||
// Transition the EFB render target to a shader resource.
|
||||
Texture2D* efb_color_texture =
|
||||
g_ActiveConfig.iMultisamples > 1 ?
|
||||
FramebufferManager::GetInstance()->GetResolvedEFBColorTexture() :
|
||||
FramebufferManager::GetInstance()->GetEFBColorTexture();
|
||||
|
||||
// Copy EFB -> backbuffer
|
||||
BlitScreen(render_pass, target_rect, scaled_efb_rect, efb_color_texture);
|
||||
}
|
||||
|
||||
void Renderer::DrawVirtualXFB(VkRenderPass render_pass, const TargetRectangle& target_rect,
|
||||
u32 xfb_addr, const XFBSourceBase* const* xfb_sources, u32 xfb_count,
|
||||
u32 fb_width, u32 fb_stride, u32 fb_height)
|
||||
{
|
||||
for (u32 i = 0; i < xfb_count; ++i)
|
||||
{
|
||||
const XFBSource* xfb_source = static_cast<const XFBSource*>(xfb_sources[i]);
|
||||
TargetRectangle source_rect = xfb_source->sourceRc;
|
||||
TargetRectangle draw_rect;
|
||||
|
||||
int xfb_width = static_cast<int>(xfb_source->srcWidth);
|
||||
int xfb_height = static_cast<int>(xfb_source->srcHeight);
|
||||
int h_offset = (static_cast<s32>(xfb_source->srcAddr) - static_cast<s32>(xfb_addr)) /
|
||||
(static_cast<s32>(fb_stride) * 2);
|
||||
draw_rect.top =
|
||||
target_rect.top + h_offset * target_rect.GetHeight() / static_cast<s32>(fb_height);
|
||||
draw_rect.bottom =
|
||||
target_rect.top +
|
||||
(h_offset + xfb_height) * target_rect.GetHeight() / static_cast<s32>(fb_height);
|
||||
draw_rect.left = target_rect.left +
|
||||
(target_rect.GetWidth() -
|
||||
xfb_width * target_rect.GetWidth() / static_cast<s32>(fb_stride)) /
|
||||
2;
|
||||
draw_rect.right = target_rect.left +
|
||||
(target_rect.GetWidth() +
|
||||
xfb_width * target_rect.GetWidth() / static_cast<s32>(fb_stride)) /
|
||||
2;
|
||||
|
||||
source_rect.right -= Renderer::EFBToScaledX(fb_stride - fb_width);
|
||||
BlitScreen(render_pass, draw_rect, source_rect,
|
||||
xfb_source->GetTexture()->GetRawTexIdentifier());
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::DrawRealXFB(VkRenderPass render_pass, const TargetRectangle& target_rect,
|
||||
const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width,
|
||||
u32 fb_stride, u32 fb_height)
|
||||
{
|
||||
for (u32 i = 0; i < xfb_count; ++i)
|
||||
{
|
||||
const XFBSource* xfb_source = static_cast<const XFBSource*>(xfb_sources[i]);
|
||||
TargetRectangle source_rect = xfb_source->sourceRc;
|
||||
TargetRectangle draw_rect = target_rect;
|
||||
source_rect.right -= fb_stride - fb_width;
|
||||
BlitScreen(render_pass, draw_rect, source_rect,
|
||||
xfb_source->GetTexture()->GetRawTexIdentifier());
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::DrawScreen(const TargetRectangle& scaled_efb_rect, u32 xfb_addr,
|
||||
const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width,
|
||||
u32 fb_stride, u32 fb_height)
|
||||
void Renderer::DrawScreen(VKTexture* xfb_texture, const EFBRectangle& xfb_region)
|
||||
{
|
||||
VkResult res;
|
||||
if (!g_command_buffer_mgr->CheckLastPresentFail())
|
||||
|
@ -767,9 +604,10 @@ void Renderer::DrawScreen(const TargetRectangle& scaled_efb_rect, u32 xfb_addr,
|
|||
vkCmdBeginRenderPass(g_command_buffer_mgr->GetCurrentCommandBuffer(), &info,
|
||||
VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
// Draw guest buffers (EFB or XFB)
|
||||
DrawFrame(m_swap_chain->GetRenderPass(), GetTargetRectangle(), scaled_efb_rect, xfb_addr,
|
||||
xfb_sources, xfb_count, fb_width, fb_stride, fb_height);
|
||||
// Draw
|
||||
TargetRectangle source_rc = xfb_texture->GetConfig().GetRect();
|
||||
BlitScreen(m_swap_chain->GetRenderPass(), GetTargetRectangle(), xfb_region,
|
||||
xfb_texture->GetRawTexIdentifier());
|
||||
|
||||
// Draw OSD
|
||||
Util::SetViewportAndScissor(g_command_buffer_mgr->GetCurrentCommandBuffer(), 0, 0,
|
||||
|
@ -787,173 +625,6 @@ void Renderer::DrawScreen(const TargetRectangle& scaled_efb_rect, u32 xfb_addr,
|
|||
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
|
||||
}
|
||||
|
||||
bool Renderer::DrawFrameDump(const TargetRectangle& scaled_efb_rect, u32 xfb_addr,
|
||||
const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width,
|
||||
u32 fb_stride, u32 fb_height, u64 ticks)
|
||||
{
|
||||
TargetRectangle target_rect = CalculateFrameDumpDrawRectangle();
|
||||
u32 width = std::max(1u, static_cast<u32>(target_rect.GetWidth()));
|
||||
u32 height = std::max(1u, static_cast<u32>(target_rect.GetHeight()));
|
||||
if (!ResizeFrameDumpBuffer(width, height))
|
||||
return false;
|
||||
|
||||
// If there was a previous frame dumped, we'll still be in TRANSFER_SRC layout.
|
||||
m_frame_dump_render_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
||||
|
||||
VkClearValue clear_value = {{{0.0f, 0.0f, 0.0f, 1.0f}}};
|
||||
VkClearRect clear_rect = {{{0, 0}, {width, height}}, 0, 1};
|
||||
VkClearAttachment clear_attachment = {VK_IMAGE_ASPECT_COLOR_BIT, 0, clear_value};
|
||||
VkRenderPassBeginInfo info = {
|
||||
VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
||||
nullptr,
|
||||
FramebufferManager::GetInstance()->GetColorCopyForReadbackRenderPass(),
|
||||
m_frame_dump_framebuffer,
|
||||
{{0, 0}, {width, height}},
|
||||
1,
|
||||
&clear_value};
|
||||
vkCmdBeginRenderPass(g_command_buffer_mgr->GetCurrentCommandBuffer(), &info,
|
||||
VK_SUBPASS_CONTENTS_INLINE);
|
||||
vkCmdClearAttachments(g_command_buffer_mgr->GetCurrentCommandBuffer(), 1, &clear_attachment, 1,
|
||||
&clear_rect);
|
||||
DrawFrame(FramebufferManager::GetInstance()->GetColorCopyForReadbackRenderPass(), target_rect,
|
||||
scaled_efb_rect, xfb_addr, xfb_sources, xfb_count, fb_width, fb_stride, fb_height);
|
||||
vkCmdEndRenderPass(g_command_buffer_mgr->GetCurrentCommandBuffer());
|
||||
|
||||
// Prepare the readback texture for copying.
|
||||
StagingTexture2D* readback_texture = PrepareFrameDumpImage(width, height, ticks);
|
||||
if (!readback_texture)
|
||||
return false;
|
||||
|
||||
// Queue a copy to the current frame dump buffer. It will be written to the frame dump later.
|
||||
m_frame_dump_render_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
||||
readback_texture->CopyFromImage(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
m_frame_dump_render_texture->GetImage(),
|
||||
VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, width, height, 0, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Renderer::StartFrameDumping()
|
||||
{
|
||||
_assert_(!m_frame_dumping_active);
|
||||
|
||||
// Register fence callback so that we know when frames are ready to be written to the dump.
|
||||
// This is done by clearing the fence pointer, so WriteFrameDumpFrame doesn't have to wait.
|
||||
auto queued_callback = [](VkCommandBuffer, VkFence) {};
|
||||
auto signaled_callback = std::bind(&Renderer::OnFrameDumpImageReady, this, std::placeholders::_1);
|
||||
|
||||
// We use the array pointer as a key here, that way if Renderer needed fence callbacks in
|
||||
// the future it could be used without conflicting.
|
||||
// We're not interested in when fences are submitted, so the first callback is a no-op.
|
||||
g_command_buffer_mgr->AddFencePointCallback(
|
||||
m_frame_dump_images.data(), std::move(queued_callback), std::move(signaled_callback));
|
||||
m_frame_dumping_active = true;
|
||||
}
|
||||
|
||||
void Renderer::EndFrameDumping()
|
||||
{
|
||||
_assert_(m_frame_dumping_active);
|
||||
|
||||
// Write any pending frames to the frame dump.
|
||||
FlushFrameDump();
|
||||
|
||||
// Remove the fence callback that we registered earlier, one less function that needs to be
|
||||
// called when preparing a command buffer.
|
||||
g_command_buffer_mgr->RemoveFencePointCallback(m_frame_dump_images.data());
|
||||
m_frame_dumping_active = false;
|
||||
}
|
||||
|
||||
void Renderer::OnFrameDumpImageReady(VkFence fence)
|
||||
{
|
||||
for (FrameDumpImage& frame : m_frame_dump_images)
|
||||
{
|
||||
// fence being a null handle means that we don't have to wait to re-use this image.
|
||||
if (frame.fence == fence)
|
||||
frame.fence = VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::WriteFrameDumpImage(size_t index)
|
||||
{
|
||||
FrameDumpImage& frame = m_frame_dump_images[index];
|
||||
_assert_(frame.pending);
|
||||
|
||||
// Check fence has been signaled.
|
||||
// The callback here should set fence to null.
|
||||
if (frame.fence != VK_NULL_HANDLE)
|
||||
{
|
||||
g_command_buffer_mgr->WaitForFence(frame.fence);
|
||||
_assert_(frame.fence == VK_NULL_HANDLE);
|
||||
}
|
||||
|
||||
// Copy the now-populated image data to the output file.
|
||||
DumpFrameData(reinterpret_cast<const u8*>(frame.readback_texture->GetMapPointer()),
|
||||
static_cast<int>(frame.readback_texture->GetWidth()),
|
||||
static_cast<int>(frame.readback_texture->GetHeight()),
|
||||
static_cast<int>(frame.readback_texture->GetRowStride()), frame.dump_state);
|
||||
|
||||
frame.pending = false;
|
||||
}
|
||||
|
||||
StagingTexture2D* Renderer::PrepareFrameDumpImage(u32 width, u32 height, u64 ticks)
|
||||
{
|
||||
// Ensure the last frame that was sent to the frame dump has completed encoding before we send
|
||||
// the next image to it.
|
||||
FinishFrameData();
|
||||
|
||||
// If the last image hasn't been written to the frame dump yet, write it now.
|
||||
// This is necessary so that the worker thread is no more than one frame behind, and the pointer
|
||||
// (which is actually the buffer) is safe for us to re-use next time.
|
||||
if (m_frame_dump_images[m_current_frame_dump_image].pending)
|
||||
WriteFrameDumpImage(m_current_frame_dump_image);
|
||||
|
||||
// Move to the next image buffer
|
||||
m_current_frame_dump_image = (m_current_frame_dump_image + 1) % FRAME_DUMP_BUFFERED_FRAMES;
|
||||
FrameDumpImage& image = m_frame_dump_images[m_current_frame_dump_image];
|
||||
|
||||
// Ensure the dimensions of the readback texture are sufficient.
|
||||
if (!image.readback_texture || width != image.readback_texture->GetWidth() ||
|
||||
height != image.readback_texture->GetHeight())
|
||||
{
|
||||
// Allocate a new readback texture.
|
||||
// The reset() call is here so that the memory is released before allocating the new texture.
|
||||
image.readback_texture.reset();
|
||||
image.readback_texture = StagingTexture2D::Create(STAGING_BUFFER_TYPE_READBACK, width, height,
|
||||
EFB_COLOR_TEXTURE_FORMAT);
|
||||
|
||||
if (!image.readback_texture || !image.readback_texture->Map())
|
||||
{
|
||||
// Not actually fatal, just means we can't dump this frame.
|
||||
PanicAlert("Failed to allocate frame dump readback texture.");
|
||||
image.readback_texture.reset();
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// The copy happens immediately after this function returns, so flag this frame as pending.
|
||||
image.fence = g_command_buffer_mgr->GetCurrentCommandBufferFence();
|
||||
image.dump_state = AVIDump::FetchState(ticks);
|
||||
image.pending = true;
|
||||
return image.readback_texture.get();
|
||||
}
|
||||
|
||||
void Renderer::FlushFrameDump()
|
||||
{
|
||||
// We must write frames in order, so this is why we use a counter rather than a range.
|
||||
for (size_t i = 0; i < FRAME_DUMP_BUFFERED_FRAMES; i++)
|
||||
{
|
||||
if (m_frame_dump_images[m_current_frame_dump_image].pending)
|
||||
WriteFrameDumpImage(m_current_frame_dump_image);
|
||||
|
||||
m_current_frame_dump_image = (m_current_frame_dump_image + 1) % FRAME_DUMP_BUFFERED_FRAMES;
|
||||
}
|
||||
|
||||
// Since everything has been written now, may as well start at index zero.
|
||||
// count-1 here because the index is incremented before usage.
|
||||
m_current_frame_dump_image = FRAME_DUMP_BUFFERED_FRAMES - 1;
|
||||
}
|
||||
|
||||
void Renderer::BlitScreen(VkRenderPass render_pass, const TargetRectangle& dst_rect,
|
||||
const TargetRectangle& src_rect, const Texture2D* src_tex)
|
||||
{
|
||||
|
@ -977,100 +648,6 @@ void Renderer::BlitScreen(VkRenderPass render_pass, const TargetRectangle& dst_r
|
|||
}
|
||||
}
|
||||
|
||||
bool Renderer::ResizeFrameDumpBuffer(u32 new_width, u32 new_height)
|
||||
{
|
||||
if (m_frame_dump_render_texture && m_frame_dump_render_texture->GetWidth() == new_width &&
|
||||
m_frame_dump_render_texture->GetHeight() == new_height)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ensure all previous frames have been dumped, since we are destroying a framebuffer
|
||||
// that may still be in use.
|
||||
FlushFrameDump();
|
||||
|
||||
if (m_frame_dump_framebuffer != VK_NULL_HANDLE)
|
||||
{
|
||||
vkDestroyFramebuffer(g_vulkan_context->GetDevice(), m_frame_dump_framebuffer, nullptr);
|
||||
m_frame_dump_framebuffer = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
m_frame_dump_render_texture =
|
||||
Texture2D::Create(new_width, new_height, 1, 1, EFB_COLOR_TEXTURE_FORMAT,
|
||||
VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_VIEW_TYPE_2D, VK_IMAGE_TILING_OPTIMAL,
|
||||
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
|
||||
|
||||
if (!m_frame_dump_render_texture)
|
||||
{
|
||||
WARN_LOG(VIDEO, "Failed to resize frame dump render texture");
|
||||
m_frame_dump_render_texture.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
VkImageView attachment = m_frame_dump_render_texture->GetView();
|
||||
VkFramebufferCreateInfo info = {};
|
||||
info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
||||
info.renderPass = FramebufferManager::GetInstance()->GetColorCopyForReadbackRenderPass();
|
||||
info.attachmentCount = 1;
|
||||
info.pAttachments = &attachment;
|
||||
info.width = new_width;
|
||||
info.height = new_height;
|
||||
info.layers = 1;
|
||||
|
||||
VkResult res =
|
||||
vkCreateFramebuffer(g_vulkan_context->GetDevice(), &info, nullptr, &m_frame_dump_framebuffer);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
WARN_LOG(VIDEO, "Failed to create frame dump framebuffer");
|
||||
m_frame_dump_render_texture.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Render pass expects texture is in transfer src to start with.
|
||||
m_frame_dump_render_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Renderer::DestroyFrameDumpResources()
|
||||
{
|
||||
if (m_frame_dump_framebuffer != VK_NULL_HANDLE)
|
||||
{
|
||||
vkDestroyFramebuffer(g_vulkan_context->GetDevice(), m_frame_dump_framebuffer, nullptr);
|
||||
m_frame_dump_framebuffer = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
m_frame_dump_render_texture.reset();
|
||||
|
||||
for (FrameDumpImage& image : m_frame_dump_images)
|
||||
{
|
||||
image.readback_texture.reset();
|
||||
image.fence = VK_NULL_HANDLE;
|
||||
image.dump_state = {};
|
||||
image.pending = false;
|
||||
}
|
||||
m_current_frame_dump_image = FRAME_DUMP_BUFFERED_FRAMES - 1;
|
||||
}
|
||||
|
||||
void Renderer::CheckForTargetResize(u32 fb_width, u32 fb_stride, u32 fb_height)
|
||||
{
|
||||
if (FramebufferManagerBase::LastXfbWidth() == fb_stride &&
|
||||
FramebufferManagerBase::LastXfbHeight() == fb_height)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
u32 new_width = (fb_stride < 1 || fb_stride > MAX_XFB_WIDTH) ? MAX_XFB_WIDTH : fb_stride;
|
||||
u32 new_height = (fb_height < 1 || fb_height > MAX_XFB_HEIGHT) ? MAX_XFB_HEIGHT : fb_height;
|
||||
FramebufferManagerBase::SetLastXfbWidth(new_width);
|
||||
FramebufferManagerBase::SetLastXfbHeight(new_height);
|
||||
|
||||
// Changing the XFB source area may alter the target size.
|
||||
if (CalculateTargetSize())
|
||||
ResizeEFBTextures();
|
||||
}
|
||||
|
||||
void Renderer::CheckForSurfaceChange()
|
||||
{
|
||||
if (!m_surface_needs_change.IsSet())
|
||||
|
@ -1144,8 +721,6 @@ void Renderer::CheckForConfigChanges()
|
|||
int old_aspect_ratio = g_ActiveConfig.iAspectRatio;
|
||||
int old_efb_scale = g_ActiveConfig.iEFBScale;
|
||||
bool old_force_filtering = g_ActiveConfig.bForceFiltering;
|
||||
bool old_use_xfb = g_ActiveConfig.bUseXFB;
|
||||
bool old_use_realxfb = g_ActiveConfig.bUseRealXFB;
|
||||
|
||||
// Copy g_Config to g_ActiveConfig.
|
||||
// NOTE: This can potentially race with the UI thread, however if it does, the changes will be
|
||||
|
@ -1157,14 +732,12 @@ void Renderer::CheckForConfigChanges()
|
|||
bool force_texture_filtering_changed = old_force_filtering != g_ActiveConfig.bForceFiltering;
|
||||
bool efb_scale_changed = old_efb_scale != g_ActiveConfig.iEFBScale;
|
||||
bool aspect_changed = old_aspect_ratio != g_ActiveConfig.iAspectRatio;
|
||||
bool use_xfb_changed = old_use_xfb != g_ActiveConfig.bUseXFB;
|
||||
bool use_realxfb_changed = old_use_realxfb != g_ActiveConfig.bUseRealXFB;
|
||||
|
||||
// Update texture cache settings with any changed options.
|
||||
TextureCache::GetInstance()->OnConfigChanged(g_ActiveConfig);
|
||||
|
||||
// Handle settings that can cause the target rectangle to change.
|
||||
if (efb_scale_changed || aspect_changed || use_xfb_changed || use_realxfb_changed)
|
||||
if (efb_scale_changed || aspect_changed)
|
||||
{
|
||||
if (CalculateTargetSize())
|
||||
ResizeEFBTextures();
|
||||
|
|
|
@ -23,6 +23,7 @@ class SwapChain;
|
|||
class StagingTexture2D;
|
||||
class Texture2D;
|
||||
class RasterFont;
|
||||
class VKTexture;
|
||||
|
||||
class Renderer : public ::Renderer
|
||||
{
|
||||
|
@ -43,8 +44,7 @@ public:
|
|||
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 color_enable, bool alpha_enable, bool z_enable,
|
||||
u32 color, u32 z) override;
|
||||
|
@ -72,7 +72,6 @@ private:
|
|||
|
||||
void BeginFrame();
|
||||
|
||||
void CheckForTargetResize(u32 fb_width, u32 fb_stride, u32 fb_height);
|
||||
void CheckForSurfaceChange();
|
||||
void CheckForConfigChanges();
|
||||
|
||||
|
@ -86,63 +85,13 @@ private:
|
|||
bool CompileShaders();
|
||||
void DestroyShaders();
|
||||
|
||||
// Transitions EFB/XFB buffers to SHADER_READ_ONLY, ready for presenting/dumping.
|
||||
// If MSAA is enabled, and XFB is disabled, also resolves the EFB buffer.
|
||||
void TransitionBuffersForSwap(const TargetRectangle& scaled_rect,
|
||||
const XFBSourceBase* const* xfb_sources, u32 xfb_count);
|
||||
|
||||
// Draw either the EFB, or specified XFB sources to the currently-bound framebuffer.
|
||||
void DrawFrame(VkRenderPass render_pass, const TargetRectangle& target_rect,
|
||||
const TargetRectangle& scaled_efb_rect, u32 xfb_addr,
|
||||
const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width,
|
||||
u32 fb_stride, u32 fb_height);
|
||||
void DrawEFB(VkRenderPass render_pass, const TargetRectangle& target_rect,
|
||||
const TargetRectangle& scaled_efb_rect);
|
||||
void DrawVirtualXFB(VkRenderPass render_pass, const TargetRectangle& target_rect, u32 xfb_addr,
|
||||
const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width,
|
||||
u32 fb_stride, u32 fb_height);
|
||||
void DrawRealXFB(VkRenderPass render_pass, const TargetRectangle& target_rect,
|
||||
const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width,
|
||||
u32 fb_stride, u32 fb_height);
|
||||
|
||||
// Draw the frame, as well as the OSD to the swap chain.
|
||||
void DrawScreen(const TargetRectangle& scaled_efb_rect, u32 xfb_addr,
|
||||
const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width,
|
||||
u32 fb_stride, u32 fb_height);
|
||||
|
||||
// Draw the frame only to the screenshot buffer.
|
||||
bool DrawFrameDump(const TargetRectangle& scaled_efb_rect, u32 xfb_addr,
|
||||
const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width,
|
||||
u32 fb_stride, u32 fb_height, u64 ticks);
|
||||
|
||||
// Sets up renderer state to permit framedumping.
|
||||
// Ideally we would have EndFrameDumping be a virtual method of Renderer, but due to various
|
||||
// design issues it would have to end up being called in the destructor, which won't work.
|
||||
void StartFrameDumping();
|
||||
void EndFrameDumping();
|
||||
|
||||
// Fence callback so that we know when frames are ready to be written to the dump.
|
||||
// This is done by clearing the fence pointer, so WriteFrameDumpFrame doesn't have to wait.
|
||||
void OnFrameDumpImageReady(VkFence fence);
|
||||
|
||||
// Writes the specified buffered frame to the frame dump.
|
||||
// NOTE: Assumes that frame.ticks and frame.pending are valid.
|
||||
void WriteFrameDumpImage(size_t index);
|
||||
|
||||
// If there is a pending frame in this buffer, writes it to the frame dump.
|
||||
// Ensures that the specified readback buffer meets the size requirements of the current frame.
|
||||
StagingTexture2D* PrepareFrameDumpImage(u32 width, u32 height, u64 ticks);
|
||||
|
||||
// Ensures all buffered frames are written to frame dump.
|
||||
void FlushFrameDump();
|
||||
void DrawScreen(VKTexture* xfb_texture, const EFBRectangle& xfb_region);
|
||||
|
||||
// Copies/scales an image to the currently-bound framebuffer.
|
||||
void BlitScreen(VkRenderPass render_pass, const TargetRectangle& dst_rect,
|
||||
const TargetRectangle& src_rect, const Texture2D* src_tex);
|
||||
|
||||
bool ResizeFrameDumpBuffer(u32 new_width, u32 new_height);
|
||||
void DestroyFrameDumpResources();
|
||||
|
||||
VkSemaphore m_image_available_semaphore = VK_NULL_HANDLE;
|
||||
VkSemaphore m_rendering_finished_semaphore = VK_NULL_HANDLE;
|
||||
|
||||
|
@ -155,22 +104,5 @@ private:
|
|||
|
||||
// Shaders used for clear/blit.
|
||||
VkShaderModule m_clear_fragment_shader = VK_NULL_HANDLE;
|
||||
|
||||
// Texture used for screenshot/frame dumping
|
||||
std::unique_ptr<Texture2D> m_frame_dump_render_texture;
|
||||
VkFramebuffer m_frame_dump_framebuffer = VK_NULL_HANDLE;
|
||||
|
||||
// Readback resources for frame dumping
|
||||
static const size_t FRAME_DUMP_BUFFERED_FRAMES = 2;
|
||||
struct FrameDumpImage
|
||||
{
|
||||
std::unique_ptr<StagingTexture2D> readback_texture;
|
||||
VkFence fence = VK_NULL_HANDLE;
|
||||
AVIDump::Frame dump_state = {};
|
||||
bool pending = false;
|
||||
};
|
||||
std::array<FrameDumpImage, FRAME_DUMP_BUFFERED_FRAMES> m_frame_dump_images;
|
||||
size_t m_current_frame_dump_image = FRAME_DUMP_BUFFERED_FRAMES - 1;
|
||||
bool m_frame_dumping_active = false;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "VideoBackends/Vulkan/TextureConverter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
@ -32,6 +33,14 @@
|
|||
|
||||
namespace Vulkan
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct EFBEncodeParams
|
||||
{
|
||||
std::array<s32, 4> position_uniform;
|
||||
float y_scale;
|
||||
};
|
||||
}
|
||||
TextureConverter::TextureConverter()
|
||||
{
|
||||
}
|
||||
|
@ -52,6 +61,8 @@ TextureConverter::~TextureConverter()
|
|||
vkDestroyBufferView(g_vulkan_context->GetDevice(), m_texel_buffer_view_r32g32_uint, nullptr);
|
||||
if (m_texel_buffer_view_rgba8_unorm != VK_NULL_HANDLE)
|
||||
vkDestroyBufferView(g_vulkan_context->GetDevice(), m_texel_buffer_view_rgba8_unorm, nullptr);
|
||||
if (m_texel_buffer_view_rgba8_uint != VK_NULL_HANDLE)
|
||||
vkDestroyBufferView(g_vulkan_context->GetDevice(), m_texel_buffer_view_rgba8_uint, nullptr);
|
||||
|
||||
if (m_encoding_render_pass != VK_NULL_HANDLE)
|
||||
vkDestroyRenderPass(g_vulkan_context->GetDevice(), m_encoding_render_pass, nullptr);
|
||||
|
@ -150,7 +161,7 @@ TextureConverter::GetCommandBufferForTextureConversion(const TextureCache::TCach
|
|||
// EFB copies can be used as paletted textures as well. For these, we can't assume them to be
|
||||
// contain the correct data before the frame begins (when the init command buffer is executed),
|
||||
// so we must convert them at the appropriate time, during the drawing command buffer.
|
||||
if (src_entry->IsEfbCopy())
|
||||
if (src_entry->IsCopy())
|
||||
{
|
||||
StateTracker::GetInstance()->EndRenderPass();
|
||||
StateTracker::GetInstance()->SetPendingRebind();
|
||||
|
@ -243,14 +254,19 @@ void TextureConverter::EncodeTextureToMemory(VkImageView src_texture, u8* dest_p
|
|||
VK_NULL_HANDLE, shader);
|
||||
|
||||
// Uniform - int4 of left,top,native_width,scale
|
||||
s32 position_uniform[4] = {src_rect.left, src_rect.top, static_cast<s32>(native_width),
|
||||
scale_by_half ? 2 : 1};
|
||||
draw.SetPushConstants(position_uniform, sizeof(position_uniform));
|
||||
EFBEncodeParams encoder_params;
|
||||
encoder_params.position_uniform[0] = src_rect.left;
|
||||
encoder_params.position_uniform[1] = src_rect.top;
|
||||
encoder_params.position_uniform[2] = static_cast<s32>(native_width);
|
||||
encoder_params.position_uniform[3] = scale_by_half ? 2 : 1;
|
||||
encoder_params.y_scale = params.y_scale;
|
||||
draw.SetPushConstants(&encoder_params, sizeof(encoder_params));
|
||||
|
||||
// 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.
|
||||
bool linear_filter = (scale_by_half && !params.depth) || g_renderer->GetEFBScale() != 1;
|
||||
bool linear_filter =
|
||||
(scale_by_half && !params.depth) || g_renderer->GetEFBScale() != 1 || params.y_scale > 1.0f;
|
||||
draw.SetPSSampler(0, src_texture, linear_filter ? g_object_cache->GetLinearSampler() :
|
||||
g_object_cache->GetPointSampler());
|
||||
|
||||
|
@ -473,11 +489,22 @@ void TextureConverter::DecodeTexture(VkCommandBuffer command_buffer,
|
|||
|
||||
// Copy/commit upload buffer.
|
||||
u32 texel_buffer_offset = static_cast<u32>(m_texel_buffer->GetCurrentOffset());
|
||||
|
||||
Util::BufferMemoryBarrier(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
m_texel_buffer->GetBuffer(), VK_ACCESS_SHADER_READ_BIT,
|
||||
VK_ACCESS_HOST_WRITE_BIT, texel_buffer_offset, total_upload_size,
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_HOST_BIT);
|
||||
|
||||
std::memcpy(m_texel_buffer->GetCurrentHostPointer(), data, data_size);
|
||||
if (has_palette)
|
||||
std::memcpy(m_texel_buffer->GetCurrentHostPointer() + palette_offset, palette, palette_size);
|
||||
m_texel_buffer->CommitMemory(total_upload_size);
|
||||
|
||||
Util::BufferMemoryBarrier(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
m_texel_buffer->GetBuffer(), VK_ACCESS_HOST_WRITE_BIT,
|
||||
VK_ACCESS_SHADER_READ_BIT, texel_buffer_offset, total_upload_size,
|
||||
VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
|
||||
|
||||
// Determine uniforms.
|
||||
PushConstants constants = {
|
||||
{width, height},
|
||||
|
@ -499,6 +526,9 @@ void TextureConverter::DecodeTexture(VkCommandBuffer command_buffer,
|
|||
case TextureConversionShader::BUFFER_FORMAT_R32G32_UINT:
|
||||
data_view = m_texel_buffer_view_r32g32_uint;
|
||||
break;
|
||||
case TextureConversionShader::BUFFER_FORMAT_RGBA8_UINT:
|
||||
data_view = m_texel_buffer_view_rgba8_uint;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -550,10 +580,12 @@ bool TextureConverter::CreateTexelBuffer()
|
|||
m_texel_buffer_view_r16_uint = CreateTexelBufferView(VK_FORMAT_R16_UINT);
|
||||
m_texel_buffer_view_r32g32_uint = CreateTexelBufferView(VK_FORMAT_R32G32_UINT);
|
||||
m_texel_buffer_view_rgba8_unorm = CreateTexelBufferView(VK_FORMAT_R8G8B8A8_UNORM);
|
||||
m_texel_buffer_view_rgba8_uint = CreateTexelBufferView(VK_FORMAT_R8G8B8A8_UINT);
|
||||
return m_texel_buffer_view_r8_uint != VK_NULL_HANDLE &&
|
||||
m_texel_buffer_view_r16_uint != VK_NULL_HANDLE &&
|
||||
m_texel_buffer_view_r32g32_uint != VK_NULL_HANDLE &&
|
||||
m_texel_buffer_view_rgba8_unorm != VK_NULL_HANDLE;
|
||||
m_texel_buffer_view_rgba8_unorm != VK_NULL_HANDLE &&
|
||||
m_texel_buffer_view_rgba8_uint != VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VkBufferView TextureConverter::CreateTexelBufferView(VkFormat format) const
|
||||
|
|
|
@ -97,6 +97,7 @@ private:
|
|||
VkBufferView m_texel_buffer_view_r8_uint = VK_NULL_HANDLE;
|
||||
VkBufferView m_texel_buffer_view_r16_uint = VK_NULL_HANDLE;
|
||||
VkBufferView m_texel_buffer_view_r32g32_uint = VK_NULL_HANDLE;
|
||||
VkBufferView m_texel_buffer_view_rgba8_uint = VK_NULL_HANDLE;
|
||||
VkBufferView m_texel_buffer_view_rgba8_unorm = VK_NULL_HANDLE;
|
||||
size_t m_texel_buffer_size = 0;
|
||||
|
||||
|
|
|
@ -113,23 +113,17 @@ void VKTexture::Bind(unsigned int stage)
|
|||
StateTracker::GetInstance()->SetTexture(stage, m_texture->GetView());
|
||||
}
|
||||
|
||||
bool VKTexture::Save(const std::string& filename, unsigned int level)
|
||||
std::optional<AbstractTexture::RawTextureInfo> VKTexture::MapFullImpl()
|
||||
{
|
||||
_assert_(level < m_config.levels);
|
||||
// No support for optimization of full copy
|
||||
return MapRegionImpl(0, 0, 0, m_config.width, m_config.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);
|
||||
|
||||
// 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 since we have to idle the GPU anyway it doesn't really matter.
|
||||
std::unique_ptr<StagingTexture2D> staging_texture = StagingTexture2D::Create(
|
||||
STAGING_BUFFER_TYPE_READBACK, level_width, level_height, TEXTURECACHE_TEXTURE_FORMAT);
|
||||
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.
|
||||
|
@ -138,9 +132,9 @@ bool VKTexture::Save(const std::string& filename, unsigned int level)
|
|||
StateTracker::GetInstance()->EndRenderPass();
|
||||
|
||||
// Copy to download buffer.
|
||||
staging_texture->CopyFromImage(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
m_texture->GetImage(), VK_IMAGE_ASPECT_COLOR_BIT, 0, 0,
|
||||
level_width, level_height, level, 0);
|
||||
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(),
|
||||
|
@ -150,21 +144,23 @@ bool VKTexture::Save(const std::string& filename, unsigned int level)
|
|||
Util::ExecuteCurrentCommandsAndRestoreState(false, true);
|
||||
|
||||
// Map the staging texture so we can copy the contents out.
|
||||
if (!staging_texture->Map())
|
||||
if (!m_staging_texture->Map())
|
||||
{
|
||||
PanicAlert("Failed to map staging texture");
|
||||
return false;
|
||||
return {};
|
||||
}
|
||||
|
||||
// Write texture out to file.
|
||||
// It's okay to throw this texture away immediately, since we're done with it, and
|
||||
// we blocked until the copy completed on the GPU anyway.
|
||||
bool result = TextureToPng(reinterpret_cast<u8*>(staging_texture->GetMapPointer()),
|
||||
static_cast<u32>(staging_texture->GetRowStride()), filename,
|
||||
level_width, level_height);
|
||||
return AbstractTexture::RawTextureInfo{reinterpret_cast<u8*>(m_staging_texture->GetMapPointer()),
|
||||
static_cast<u32>(m_staging_texture->GetRowStride()), width,
|
||||
height};
|
||||
}
|
||||
|
||||
staging_texture->Unmap();
|
||||
return result;
|
||||
void VKTexture::Unmap()
|
||||
{
|
||||
if (!m_staging_texture)
|
||||
return;
|
||||
|
||||
m_staging_texture->Unmap();
|
||||
}
|
||||
|
||||
void VKTexture::CopyTextureRectangle(const MathUtil::Rectangle<int>& dst_rect,
|
||||
|
|
|
@ -20,7 +20,7 @@ public:
|
|||
~VKTexture();
|
||||
|
||||
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,
|
||||
|
@ -47,7 +47,12 @@ private:
|
|||
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;
|
||||
|
||||
std::unique_ptr<Texture2D> m_texture;
|
||||
std::unique_ptr<StagingTexture2D> m_staging_texture;
|
||||
VkFramebuffer m_framebuffer;
|
||||
};
|
||||
|
||||
|
|
|
@ -246,6 +246,8 @@ void VulkanContext::PopulateBackendInfo(VideoConfig* config)
|
|||
config->backend_info.bSupportsST3CTextures = false; // Dependent on features.
|
||||
config->backend_info.bSupportsBPTCTextures = false; // Dependent on features.
|
||||
config->backend_info.bSupportsReversedDepthRange = false; // No support yet due to driver bugs.
|
||||
config->backend_info.bSupportsCopyToVram = true; // Assumed support.
|
||||
config->backend_info.bForceCopyToRam = false;
|
||||
}
|
||||
|
||||
void VulkanContext::PopulateBackendInfoAdapters(VideoConfig* config, const GPUList& gpu_list)
|
||||
|
|
|
@ -4,9 +4,12 @@
|
|||
|
||||
#include <algorithm>
|
||||
|
||||
#include "VideoCommon/AbstractTexture.h"
|
||||
#include "Common/Assert.h"
|
||||
|
||||
AbstractTexture::AbstractTexture(const TextureConfig& c) : m_config(c)
|
||||
#include "VideoCommon/AbstractTexture.h"
|
||||
#include "VideoCommon/ImageWrite.h"
|
||||
|
||||
AbstractTexture::AbstractTexture(const TextureConfig& c) : m_config(c), m_currently_mapped(false)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -14,7 +17,87 @@ AbstractTexture::~AbstractTexture() = default;
|
|||
|
||||
bool AbstractTexture::Save(const std::string& filename, unsigned int level)
|
||||
{
|
||||
return false;
|
||||
// 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);
|
||||
|
||||
auto result = level == 0 ? Map() : Map(level);
|
||||
|
||||
if (!result.has_value())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto raw_data = result.value();
|
||||
return TextureToPng(raw_data.data, raw_data.stride, filename, raw_data.width, raw_data.height);
|
||||
}
|
||||
|
||||
std::optional<AbstractTexture::RawTextureInfo> AbstractTexture::Map()
|
||||
{
|
||||
if (m_currently_mapped)
|
||||
{
|
||||
Unmap();
|
||||
m_currently_mapped = false;
|
||||
}
|
||||
auto result = MapFullImpl();
|
||||
|
||||
if (!result.has_value())
|
||||
{
|
||||
m_currently_mapped = false;
|
||||
return {};
|
||||
}
|
||||
|
||||
m_currently_mapped = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<AbstractTexture::RawTextureInfo> AbstractTexture::Map(u32 level, u32 x, u32 y,
|
||||
u32 width, u32 height)
|
||||
{
|
||||
_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())
|
||||
{
|
||||
m_currently_mapped = false;
|
||||
return {};
|
||||
}
|
||||
|
||||
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 {};
|
||||
}
|
||||
|
||||
bool AbstractTexture::IsCompressedHostTextureFormat(AbstractTextureFormat format)
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
@ -17,7 +18,20 @@ public:
|
|||
explicit AbstractTexture(const TextureConfig& c);
|
||||
virtual ~AbstractTexture();
|
||||
virtual void Bind(unsigned int stage) = 0;
|
||||
virtual bool Save(const std::string& filename, unsigned int level);
|
||||
bool Save(const std::string& filename, unsigned int level);
|
||||
|
||||
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,
|
||||
|
@ -31,5 +45,10 @@ public:
|
|||
const TextureConfig& GetConfig() const;
|
||||
|
||||
protected:
|
||||
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;
|
||||
|
||||
const TextureConfig m_config;
|
||||
};
|
||||
|
|
|
@ -12,8 +12,11 @@
|
|||
#include "Common/StringUtil.h"
|
||||
#include "Common/Thread.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/CoreTiming.h"
|
||||
#include "Core/FifoPlayer/FifoPlayer.h"
|
||||
#include "Core/FifoPlayer/FifoRecorder.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/HW/VideoInterface.h"
|
||||
|
||||
#include "VideoCommon/BPFunctions.h"
|
||||
#include "VideoCommon/BPMemory.h"
|
||||
|
@ -230,7 +233,7 @@ static void BPWritten(const BPCmd& bp)
|
|||
bool is_depth_copy = bpmem.zcontrol.pixel_format == PEControl::Z24;
|
||||
g_texture_cache->CopyRenderTargetToTexture(destAddr, PE_copy.tp_realFormat(), destStride,
|
||||
is_depth_copy, srcRect, !!PE_copy.intensity_fmt,
|
||||
!!PE_copy.half_scale);
|
||||
!!PE_copy.half_scale, 1.0f, 1.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -250,17 +253,33 @@ static void BPWritten(const BPCmd& bp)
|
|||
float num_xfb_lines = 1.0f + bpmem.copyTexSrcWH.y * yScale;
|
||||
|
||||
u32 height = static_cast<u32>(num_xfb_lines);
|
||||
if (height > MAX_XFB_HEIGHT)
|
||||
{
|
||||
INFO_LOG(VIDEO, "Tried to scale EFB to too many XFB lines: %d (%f)", height, num_xfb_lines);
|
||||
height = MAX_XFB_HEIGHT;
|
||||
}
|
||||
|
||||
DEBUG_LOG(VIDEO, "RenderToXFB: destAddr: %08x | srcRect {%d %d %d %d} | fbWidth: %u | "
|
||||
"fbStride: %u | fbHeight: %u",
|
||||
destAddr, srcRect.left, srcRect.top, srcRect.right, srcRect.bottom,
|
||||
bpmem.copyTexSrcWH.x + 1, destStride, height);
|
||||
|
||||
bool is_depth_copy = bpmem.zcontrol.pixel_format == PEControl::Z24;
|
||||
g_texture_cache->CopyRenderTargetToTexture(destAddr, EFBCopyFormat::XFB, destStride,
|
||||
is_depth_copy, srcRect, false, false, yScale,
|
||||
s_gammaLUT[PE_copy.gamma]);
|
||||
|
||||
// This stays in to signal end of a "frame"
|
||||
g_renderer->RenderToXFB(destAddr, srcRect, destStride, height, s_gammaLUT[PE_copy.gamma]);
|
||||
|
||||
if (g_ActiveConfig.bImmediateXFB)
|
||||
{
|
||||
// below div two to convert from bytes to pixels - it expects width, not stride
|
||||
g_renderer->Swap(destAddr, destStride / 2, destStride / 2, height, srcRect,
|
||||
CoreTiming::GetTicks());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FifoPlayer::GetInstance().IsRunningWithFakeVideoInterfaceUpdates())
|
||||
{
|
||||
VideoInterface::FakeVIUpdate(destAddr, srcRect.GetWidth(), height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the rectangular region after copying it.
|
||||
|
|
|
@ -14,252 +14,6 @@
|
|||
|
||||
std::unique_ptr<FramebufferManagerBase> g_framebuffer_manager;
|
||||
|
||||
std::unique_ptr<XFBSourceBase>
|
||||
FramebufferManagerBase::m_realXFBSource; // Only used in Real XFB mode
|
||||
FramebufferManagerBase::VirtualXFBListType
|
||||
FramebufferManagerBase::m_virtualXFBList; // Only used in Virtual XFB mode
|
||||
std::array<const XFBSourceBase*, FramebufferManagerBase::MAX_VIRTUAL_XFB>
|
||||
FramebufferManagerBase::m_overlappingXFBArray;
|
||||
|
||||
unsigned int FramebufferManagerBase::s_last_xfb_width = 1;
|
||||
unsigned int FramebufferManagerBase::s_last_xfb_height = 1;
|
||||
|
||||
unsigned int FramebufferManagerBase::m_EFBLayers = 1;
|
||||
|
||||
FramebufferManagerBase::FramebufferManagerBase()
|
||||
{
|
||||
// Can't hurt
|
||||
m_overlappingXFBArray.fill(nullptr);
|
||||
}
|
||||
|
||||
FramebufferManagerBase::~FramebufferManagerBase()
|
||||
{
|
||||
// Necessary, as these are static members
|
||||
// (they really shouldn't be and should be refactored at some point).
|
||||
m_virtualXFBList.clear();
|
||||
m_realXFBSource.reset();
|
||||
}
|
||||
|
||||
const XFBSourceBase* const* FramebufferManagerBase::GetXFBSource(u32 xfbAddr, u32 fbWidth,
|
||||
u32 fbHeight, u32* xfbCountP)
|
||||
{
|
||||
if (!g_ActiveConfig.bUseXFB)
|
||||
return nullptr;
|
||||
|
||||
if (g_ActiveConfig.bUseRealXFB)
|
||||
return GetRealXFBSource(xfbAddr, fbWidth, fbHeight, xfbCountP);
|
||||
else
|
||||
return GetVirtualXFBSource(xfbAddr, fbWidth, fbHeight, xfbCountP);
|
||||
}
|
||||
|
||||
const XFBSourceBase* const* FramebufferManagerBase::GetRealXFBSource(u32 xfbAddr, u32 fbWidth,
|
||||
u32 fbHeight, u32* xfbCountP)
|
||||
{
|
||||
*xfbCountP = 1;
|
||||
|
||||
// recreate if needed
|
||||
if (m_realXFBSource &&
|
||||
(m_realXFBSource->texWidth != fbWidth || m_realXFBSource->texHeight != fbHeight))
|
||||
m_realXFBSource.reset();
|
||||
|
||||
if (!m_realXFBSource && g_framebuffer_manager)
|
||||
m_realXFBSource = g_framebuffer_manager->CreateXFBSource(fbWidth, fbHeight, 1);
|
||||
|
||||
if (!m_realXFBSource)
|
||||
return nullptr;
|
||||
|
||||
m_realXFBSource->srcAddr = xfbAddr;
|
||||
|
||||
m_realXFBSource->srcWidth = MAX_XFB_WIDTH;
|
||||
m_realXFBSource->srcHeight = MAX_XFB_HEIGHT;
|
||||
|
||||
m_realXFBSource->texWidth = fbWidth;
|
||||
m_realXFBSource->texHeight = fbHeight;
|
||||
|
||||
m_realXFBSource->sourceRc.left = 0;
|
||||
m_realXFBSource->sourceRc.top = 0;
|
||||
m_realXFBSource->sourceRc.right = fbWidth;
|
||||
m_realXFBSource->sourceRc.bottom = fbHeight;
|
||||
|
||||
// Decode YUYV data from GameCube RAM
|
||||
m_realXFBSource->DecodeToTexture(xfbAddr, fbWidth, fbHeight);
|
||||
|
||||
m_overlappingXFBArray[0] = m_realXFBSource.get();
|
||||
return &m_overlappingXFBArray[0];
|
||||
}
|
||||
|
||||
const XFBSourceBase* const*
|
||||
FramebufferManagerBase::GetVirtualXFBSource(u32 xfbAddr, u32 fbWidth, u32 fbHeight, u32* xfbCountP)
|
||||
{
|
||||
u32 xfbCount = 0;
|
||||
|
||||
if (m_virtualXFBList.empty()) // no Virtual XFBs available
|
||||
return nullptr;
|
||||
|
||||
u32 srcLower = xfbAddr;
|
||||
u32 srcUpper = xfbAddr + 2 * fbWidth * fbHeight;
|
||||
|
||||
VirtualXFBListType::reverse_iterator it = m_virtualXFBList.rbegin(),
|
||||
vlend = m_virtualXFBList.rend();
|
||||
for (; it != vlend; ++it)
|
||||
{
|
||||
VirtualXFB* vxfb = &*it;
|
||||
|
||||
u32 dstLower = vxfb->xfbAddr;
|
||||
u32 dstUpper = vxfb->xfbAddr + 2 * vxfb->xfbWidth * vxfb->xfbHeight;
|
||||
|
||||
if (AddressRangesOverlap(srcLower, srcUpper, dstLower, dstUpper))
|
||||
{
|
||||
m_overlappingXFBArray[xfbCount] = vxfb->xfbSource.get();
|
||||
++xfbCount;
|
||||
}
|
||||
}
|
||||
|
||||
*xfbCountP = xfbCount;
|
||||
return &m_overlappingXFBArray[0];
|
||||
}
|
||||
|
||||
void FramebufferManagerBase::CopyToXFB(u32 xfbAddr, u32 fbStride, u32 fbHeight,
|
||||
const EFBRectangle& sourceRc, float Gamma)
|
||||
{
|
||||
if (g_ActiveConfig.bUseRealXFB)
|
||||
{
|
||||
if (g_framebuffer_manager)
|
||||
g_framebuffer_manager->CopyToRealXFB(xfbAddr, fbStride, fbHeight, sourceRc, Gamma);
|
||||
}
|
||||
else
|
||||
{
|
||||
CopyToVirtualXFB(xfbAddr, fbStride, fbHeight, sourceRc, Gamma);
|
||||
}
|
||||
}
|
||||
|
||||
void FramebufferManagerBase::CopyToVirtualXFB(u32 xfbAddr, u32 fbStride, u32 fbHeight,
|
||||
const EFBRectangle& sourceRc, float Gamma)
|
||||
{
|
||||
if (!g_framebuffer_manager)
|
||||
return;
|
||||
|
||||
VirtualXFBListType::iterator vxfb = FindVirtualXFB(xfbAddr, sourceRc.GetWidth(), fbHeight);
|
||||
|
||||
if (m_virtualXFBList.end() == vxfb)
|
||||
{
|
||||
if (m_virtualXFBList.size() < MAX_VIRTUAL_XFB)
|
||||
{
|
||||
// create a new Virtual XFB and place it at the front of the list
|
||||
m_virtualXFBList.emplace_front();
|
||||
vxfb = m_virtualXFBList.begin();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Replace the last virtual XFB
|
||||
--vxfb;
|
||||
}
|
||||
}
|
||||
// else // replace existing virtual XFB
|
||||
|
||||
// move this Virtual XFB to the front of the list.
|
||||
if (m_virtualXFBList.begin() != vxfb)
|
||||
m_virtualXFBList.splice(m_virtualXFBList.begin(), m_virtualXFBList, vxfb);
|
||||
|
||||
u32 target_width, target_height;
|
||||
std::tie(target_width, target_height) = g_framebuffer_manager->GetTargetSize();
|
||||
|
||||
// recreate if needed
|
||||
if (vxfb->xfbSource &&
|
||||
(vxfb->xfbSource->texWidth != target_width || vxfb->xfbSource->texHeight != target_height))
|
||||
vxfb->xfbSource.reset();
|
||||
|
||||
if (!vxfb->xfbSource)
|
||||
{
|
||||
vxfb->xfbSource =
|
||||
g_framebuffer_manager->CreateXFBSource(target_width, target_height, m_EFBLayers);
|
||||
if (!vxfb->xfbSource)
|
||||
return;
|
||||
|
||||
vxfb->xfbSource->texWidth = target_width;
|
||||
vxfb->xfbSource->texHeight = target_height;
|
||||
}
|
||||
|
||||
vxfb->xfbSource->srcAddr = vxfb->xfbAddr = xfbAddr;
|
||||
vxfb->xfbSource->srcWidth = vxfb->xfbWidth = sourceRc.GetWidth();
|
||||
vxfb->xfbSource->srcHeight = vxfb->xfbHeight = fbHeight;
|
||||
|
||||
vxfb->xfbSource->sourceRc = g_renderer->ConvertEFBRectangle(sourceRc);
|
||||
|
||||
// keep stale XFB data from being used
|
||||
ReplaceVirtualXFB();
|
||||
|
||||
// Copy EFB data to XFB and restore render target again
|
||||
vxfb->xfbSource->CopyEFB(Gamma);
|
||||
}
|
||||
|
||||
FramebufferManagerBase::VirtualXFBListType::iterator
|
||||
FramebufferManagerBase::FindVirtualXFB(u32 xfbAddr, u32 width, u32 height)
|
||||
{
|
||||
const u32 srcLower = xfbAddr;
|
||||
const u32 srcUpper = xfbAddr + 2 * width * height;
|
||||
|
||||
return std::find_if(m_virtualXFBList.begin(), m_virtualXFBList.end(),
|
||||
[srcLower, srcUpper](const VirtualXFB& xfb) {
|
||||
const u32 dstLower = xfb.xfbAddr;
|
||||
const u32 dstUpper = xfb.xfbAddr + 2 * xfb.xfbWidth * xfb.xfbHeight;
|
||||
|
||||
return dstLower >= srcLower && dstUpper <= srcUpper;
|
||||
});
|
||||
}
|
||||
|
||||
void FramebufferManagerBase::ReplaceVirtualXFB()
|
||||
{
|
||||
VirtualXFBListType::iterator it = m_virtualXFBList.begin();
|
||||
|
||||
const s32 srcLower = it->xfbAddr;
|
||||
const s32 srcUpper = it->xfbAddr + 2 * it->xfbWidth * it->xfbHeight;
|
||||
const s32 lineSize = 2 * it->xfbWidth;
|
||||
|
||||
++it;
|
||||
|
||||
for (; it != m_virtualXFBList.end(); ++it)
|
||||
{
|
||||
s32 dstLower = it->xfbAddr;
|
||||
s32 dstUpper = it->xfbAddr + 2 * it->xfbWidth * it->xfbHeight;
|
||||
|
||||
if (dstLower >= srcLower && dstUpper <= srcUpper)
|
||||
{
|
||||
// Invalidate the data
|
||||
it->xfbAddr = 0;
|
||||
it->xfbHeight = 0;
|
||||
it->xfbWidth = 0;
|
||||
}
|
||||
else if (AddressRangesOverlap(srcLower, srcUpper, dstLower, dstUpper))
|
||||
{
|
||||
s32 upperOverlap = (srcUpper - dstLower) / lineSize;
|
||||
s32 lowerOverlap = (dstUpper - srcLower) / lineSize;
|
||||
|
||||
if (upperOverlap > 0 && lowerOverlap < 0)
|
||||
{
|
||||
it->xfbAddr += lineSize * upperOverlap;
|
||||
it->xfbHeight -= upperOverlap;
|
||||
}
|
||||
else if (lowerOverlap > 0)
|
||||
{
|
||||
it->xfbHeight -= lowerOverlap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int FramebufferManagerBase::ScaleToVirtualXfbWidth(int x, const TargetRectangle& target_rectangle)
|
||||
{
|
||||
if (g_ActiveConfig.RealXFBEnabled())
|
||||
return x;
|
||||
|
||||
return x * target_rectangle.GetWidth() / s_last_xfb_width;
|
||||
}
|
||||
|
||||
int FramebufferManagerBase::ScaleToVirtualXfbHeight(int y, const TargetRectangle& target_rectangle)
|
||||
{
|
||||
if (g_ActiveConfig.RealXFBEnabled())
|
||||
return y;
|
||||
|
||||
return y * target_rectangle.GetHeight() / s_last_xfb_height;
|
||||
}
|
||||
FramebufferManagerBase::~FramebufferManagerBase() = default;
|
||||
|
|
|
@ -17,94 +17,14 @@ inline bool AddressRangesOverlap(u32 aLower, u32 aUpper, u32 bLower, u32 bUpper)
|
|||
return !((aLower >= bUpper) || (bLower >= aUpper));
|
||||
}
|
||||
|
||||
struct XFBSourceBase
|
||||
{
|
||||
virtual ~XFBSourceBase() {}
|
||||
virtual void DecodeToTexture(u32 xfbAddr, u32 fbWidth, u32 fbHeight) = 0;
|
||||
|
||||
virtual void CopyEFB(float Gamma) = 0;
|
||||
|
||||
u32 srcAddr;
|
||||
u32 srcWidth;
|
||||
u32 srcHeight;
|
||||
|
||||
unsigned int texWidth;
|
||||
unsigned int texHeight;
|
||||
|
||||
// TODO: only used by OGL
|
||||
TargetRectangle sourceRc;
|
||||
};
|
||||
|
||||
class FramebufferManagerBase
|
||||
{
|
||||
public:
|
||||
enum
|
||||
{
|
||||
// There may be multiple XFBs in GameCube RAM. This is the maximum number to
|
||||
// virtualize.
|
||||
MAX_VIRTUAL_XFB = 8
|
||||
};
|
||||
|
||||
FramebufferManagerBase();
|
||||
virtual ~FramebufferManagerBase();
|
||||
|
||||
static void CopyToXFB(u32 xfbAddr, u32 fbStride, u32 fbHeight, const EFBRectangle& sourceRc,
|
||||
float Gamma);
|
||||
static const XFBSourceBase* const* GetXFBSource(u32 xfbAddr, u32 fbWidth, u32 fbHeight,
|
||||
u32* xfbCount);
|
||||
|
||||
static void SetLastXfbWidth(unsigned int width) { s_last_xfb_width = width; }
|
||||
static void SetLastXfbHeight(unsigned int height) { s_last_xfb_height = height; }
|
||||
static unsigned int LastXfbWidth() { return s_last_xfb_width; }
|
||||
static unsigned int LastXfbHeight() { return s_last_xfb_height; }
|
||||
static int ScaleToVirtualXfbWidth(int x, const TargetRectangle& target_rectangle);
|
||||
static int ScaleToVirtualXfbHeight(int y, const TargetRectangle& target_rectangle);
|
||||
|
||||
static unsigned int GetEFBLayers() { return m_EFBLayers; }
|
||||
virtual std::pair<u32, u32> GetTargetSize() const = 0;
|
||||
|
||||
protected:
|
||||
struct VirtualXFB
|
||||
{
|
||||
VirtualXFB() {}
|
||||
// Address and size in GameCube RAM
|
||||
u32 xfbAddr = 0;
|
||||
u32 xfbWidth = 0;
|
||||
u32 xfbHeight = 0;
|
||||
|
||||
std::unique_ptr<XFBSourceBase> xfbSource;
|
||||
};
|
||||
|
||||
typedef std::list<VirtualXFB> VirtualXFBListType;
|
||||
|
||||
static unsigned int m_EFBLayers;
|
||||
|
||||
private:
|
||||
virtual std::unique_ptr<XFBSourceBase>
|
||||
CreateXFBSource(unsigned int target_width, unsigned int target_height, unsigned int layers) = 0;
|
||||
|
||||
static VirtualXFBListType::iterator FindVirtualXFB(u32 xfbAddr, u32 width, u32 height);
|
||||
|
||||
static void ReplaceVirtualXFB();
|
||||
|
||||
// TODO: merge these virtual funcs, they are nearly all the same
|
||||
virtual void CopyToRealXFB(u32 xfbAddr, u32 fbStride, u32 fbHeight, const EFBRectangle& sourceRc,
|
||||
float Gamma = 1.0f) = 0;
|
||||
static void CopyToVirtualXFB(u32 xfbAddr, u32 fbWidth, u32 fbHeight, const EFBRectangle& sourceRc,
|
||||
float Gamma = 1.0f);
|
||||
|
||||
static const XFBSourceBase* const* GetRealXFBSource(u32 xfbAddr, u32 fbWidth, u32 fbHeight,
|
||||
u32* xfbCount);
|
||||
static const XFBSourceBase* const* GetVirtualXFBSource(u32 xfbAddr, u32 fbWidth, u32 fbHeight,
|
||||
u32* xfbCount);
|
||||
|
||||
static std::unique_ptr<XFBSourceBase> m_realXFBSource; // Only used in Real XFB mode
|
||||
static VirtualXFBListType m_virtualXFBList; // Only used in Virtual XFB mode
|
||||
|
||||
static std::array<const XFBSourceBase*, MAX_VIRTUAL_XFB> m_overlappingXFBArray;
|
||||
|
||||
static unsigned int s_last_xfb_width;
|
||||
static unsigned int s_last_xfb_height;
|
||||
};
|
||||
|
||||
extern std::unique_ptr<FramebufferManagerBase> g_framebuffer_manager;
|
||||
|
|
|
@ -45,11 +45,20 @@ void VideoBackendBase::Video_ExitLoop()
|
|||
s_FifoShuttingDown.Set();
|
||||
}
|
||||
|
||||
void VideoBackendBase::Video_CleanupShared()
|
||||
{
|
||||
// First stop any framedumping, which might need to dump the last xfb frame. This process
|
||||
// can require additional graphics sub-systems so it needs to be done first
|
||||
g_renderer->ExitFramedumping();
|
||||
|
||||
Video_Cleanup();
|
||||
}
|
||||
|
||||
// Run from the CPU thread (from VideoInterface.cpp)
|
||||
void VideoBackendBase::Video_BeginField(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight,
|
||||
u64 ticks)
|
||||
{
|
||||
if (m_initialized && g_ActiveConfig.bUseXFB && g_renderer)
|
||||
if (m_initialized && g_renderer && !g_ActiveConfig.bImmediateXFB)
|
||||
{
|
||||
Fifo::SyncGPU(Fifo::SyncGPUReason::Swap);
|
||||
|
||||
|
|
|
@ -37,13 +37,13 @@
|
|||
#include "Core/Config/SYSCONFSettings.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/CoreTiming.h"
|
||||
#include "Core/FifoPlayer/FifoRecorder.h"
|
||||
#include "Core/HW/VideoInterface.h"
|
||||
#include "Core/Host.h"
|
||||
#include "Core/Movie.h"
|
||||
|
||||
#include "VideoCommon/AVIDump.h"
|
||||
#include "VideoCommon/AbstractTexture.h"
|
||||
#include "VideoCommon/BPMemory.h"
|
||||
#include "VideoCommon/CPMemory.h"
|
||||
#include "VideoCommon/CommandProcessor.h"
|
||||
|
@ -84,9 +84,6 @@ static float AspectToWidescreen(float aspect)
|
|||
Renderer::Renderer(int backbuffer_width, int backbuffer_height)
|
||||
: m_backbuffer_width(backbuffer_width), m_backbuffer_height(backbuffer_height)
|
||||
{
|
||||
FramebufferManagerBase::SetLastXfbWidth(MAX_XFB_WIDTH);
|
||||
FramebufferManagerBase::SetLastXfbHeight(MAX_XFB_HEIGHT);
|
||||
|
||||
UpdateActiveConfig();
|
||||
UpdateDrawRectangle();
|
||||
CalculateTargetSize();
|
||||
|
@ -100,11 +97,15 @@ Renderer::Renderer(int backbuffer_width, int backbuffer_height)
|
|||
m_last_host_config_bits = ShaderHostConfig::GetCurrent().bits;
|
||||
}
|
||||
|
||||
Renderer::~Renderer()
|
||||
Renderer::~Renderer() = default;
|
||||
|
||||
void Renderer::ExitFramedumping()
|
||||
{
|
||||
ShutdownFrameDumping();
|
||||
if (m_frame_dump_thread.joinable())
|
||||
m_frame_dump_thread.join();
|
||||
|
||||
m_dump_texture.reset();
|
||||
}
|
||||
|
||||
void Renderer::RenderToXFB(u32 xfbAddr, const EFBRectangle& sourceRc, u32 fbStride, u32 fbHeight,
|
||||
|
@ -114,21 +115,6 @@ void Renderer::RenderToXFB(u32 xfbAddr, const EFBRectangle& sourceRc, u32 fbStri
|
|||
|
||||
if (!fbStride || !fbHeight)
|
||||
return;
|
||||
|
||||
m_xfb_written = true;
|
||||
|
||||
if (g_ActiveConfig.bUseXFB)
|
||||
{
|
||||
FramebufferManagerBase::CopyToXFB(xfbAddr, fbStride, fbHeight, sourceRc, Gamma);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The timing is not predictable here. So try to use the XFB path to dump frames.
|
||||
u64 ticks = CoreTiming::GetTicks();
|
||||
|
||||
// below div two to convert from bytes to pixels - it expects width, not stride
|
||||
Swap(xfbAddr, fbStride / 2, fbStride / 2, fbHeight, sourceRc, ticks, Gamma);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int Renderer::GetEFBScale() const
|
||||
|
@ -167,8 +153,8 @@ bool Renderer::CalculateTargetSize()
|
|||
if (g_ActiveConfig.iEFBScale == EFB_SCALE_AUTO_INTEGRAL)
|
||||
{
|
||||
// Set a scale based on the window size
|
||||
int width = FramebufferManagerBase::ScaleToVirtualXfbWidth(EFB_WIDTH, m_target_rectangle);
|
||||
int height = FramebufferManagerBase::ScaleToVirtualXfbHeight(EFB_HEIGHT, m_target_rectangle);
|
||||
int width = EFB_WIDTH * m_target_rectangle.GetWidth() / m_last_xfb_width;
|
||||
int height = EFB_HEIGHT * m_target_rectangle.GetWidth() / m_last_xfb_height;
|
||||
m_efb_scale = std::max((width - 1) / EFB_WIDTH + 1, (height - 1) / EFB_HEIGHT + 1);
|
||||
}
|
||||
else
|
||||
|
@ -342,6 +328,7 @@ void Renderer::DrawDebugText()
|
|||
}
|
||||
|
||||
const char* const efbcopy_text = g_ActiveConfig.bSkipEFBCopyToRam ? "to Texture" : "to RAM";
|
||||
const char* const xfbcopy_text = g_ActiveConfig.bSkipXFBCopyToRam ? "to Texture" : "to RAM";
|
||||
|
||||
// The rows
|
||||
const std::string lines[] = {
|
||||
|
@ -353,6 +340,8 @@ void Renderer::DrawDebugText()
|
|||
"Speed Limit: Unlimited" :
|
||||
StringFromFormat("Speed Limit: %li%%",
|
||||
std::lround(SConfig::GetInstance().m_EmulationSpeed * 100.f)),
|
||||
std::string("Copy XFB: ") + xfbcopy_text +
|
||||
(g_ActiveConfig.bImmediateXFB ? " (Immediate)" : ""),
|
||||
};
|
||||
|
||||
enum
|
||||
|
@ -425,36 +414,6 @@ std::tuple<float, float> Renderer::ScaleToDisplayAspectRatio(const int width,
|
|||
return std::make_tuple(scaled_width, scaled_height);
|
||||
}
|
||||
|
||||
TargetRectangle Renderer::CalculateFrameDumpDrawRectangle() const
|
||||
{
|
||||
// No point including any borders in the frame dump image, since they'd have to be cropped anyway.
|
||||
TargetRectangle rc;
|
||||
rc.left = 0;
|
||||
rc.top = 0;
|
||||
|
||||
// If full-resolution frame dumping is disabled, just use the window draw rectangle.
|
||||
// Also do this if RealXFB is enabled, since the image has been downscaled for the XFB copy
|
||||
// anyway, and there's no point writing an upscaled frame with no filtering.
|
||||
if (!g_ActiveConfig.bInternalResolutionFrameDumps || g_ActiveConfig.RealXFBEnabled())
|
||||
{
|
||||
// But still remove the borders, since the caller expects this.
|
||||
rc.right = m_target_rectangle.GetWidth();
|
||||
rc.bottom = m_target_rectangle.GetHeight();
|
||||
return rc;
|
||||
}
|
||||
|
||||
// Grab the dimensions of the EFB textures, we scale either of these depending on the ratio.
|
||||
u32 efb_width, efb_height;
|
||||
std::tie(efb_width, efb_height) = g_framebuffer_manager->GetTargetSize();
|
||||
|
||||
float draw_width, draw_height;
|
||||
std::tie(draw_width, draw_height) = ScaleToDisplayAspectRatio(efb_width, efb_height);
|
||||
|
||||
rc.right = static_cast<int>(std::ceil(draw_width));
|
||||
rc.bottom = static_cast<int>(std::ceil(draw_height));
|
||||
return rc;
|
||||
}
|
||||
|
||||
void Renderer::UpdateDrawRectangle()
|
||||
{
|
||||
// The rendering window size
|
||||
|
@ -564,13 +523,26 @@ void Renderer::UpdateDrawRectangle()
|
|||
|
||||
void Renderer::SetWindowSize(int width, int height)
|
||||
{
|
||||
width = std::max(width, 1);
|
||||
height = std::max(height, 1);
|
||||
|
||||
// Scale the window size by the EFB scale.
|
||||
if (g_ActiveConfig.iEFBScale != EFB_SCALE_AUTO_INTEGRAL)
|
||||
std::tie(width, height) = CalculateTargetScale(width, height);
|
||||
|
||||
std::tie(width, height) = CalculateOutputDimensions(width, height);
|
||||
|
||||
// Track the last values of width/height to avoid sending a window resize event every frame.
|
||||
if (width != m_last_window_request_width || height != m_last_window_request_height)
|
||||
{
|
||||
m_last_window_request_width = width;
|
||||
m_last_window_request_height = height;
|
||||
Host_RequestRenderWindowSize(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
std::tuple<int, int> Renderer::CalculateOutputDimensions(int width, int height)
|
||||
{
|
||||
width = std::max(width, 1);
|
||||
height = std::max(height, 1);
|
||||
|
||||
float scaled_width, scaled_height;
|
||||
std::tie(scaled_width, scaled_height) = ScaleToDisplayAspectRatio(width, height);
|
||||
|
||||
|
@ -602,13 +574,7 @@ void Renderer::SetWindowSize(int width, int height)
|
|||
width -= width % 4;
|
||||
height -= height % 4;
|
||||
|
||||
// Track the last values of width/height to avoid sending a window resize event every frame.
|
||||
if (width != m_last_window_request_width || height != m_last_window_request_height)
|
||||
{
|
||||
m_last_window_request_width = width;
|
||||
m_last_window_request_height = height;
|
||||
Host_RequestRenderWindowSize(width, height);
|
||||
}
|
||||
return std::make_tuple(width, height);
|
||||
}
|
||||
|
||||
void Renderer::CheckFifoRecording()
|
||||
|
@ -645,7 +611,7 @@ void Renderer::RecordVideoMemory()
|
|||
}
|
||||
|
||||
void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const EFBRectangle& rc,
|
||||
u64 ticks, float Gamma)
|
||||
u64 ticks)
|
||||
{
|
||||
// Heuristic to detect if a GameCube game is in 16:9 anamorphic widescreen mode.
|
||||
if (!SConfig::GetInstance().bWii)
|
||||
|
@ -663,11 +629,49 @@ void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const
|
|||
m_aspect_wide = flush_count_anamorphic > 0.75 * flush_total;
|
||||
}
|
||||
|
||||
// TODO: merge more generic parts into VideoCommon
|
||||
SwapImpl(xfbAddr, fbWidth, fbStride, fbHeight, rc, ticks, Gamma);
|
||||
if (IsFrameDumping() && m_last_xfb_texture)
|
||||
{
|
||||
FinishFrameData();
|
||||
}
|
||||
else
|
||||
{
|
||||
ShutdownFrameDumping();
|
||||
}
|
||||
|
||||
if (m_xfb_written)
|
||||
m_fps_counter.Update();
|
||||
bool update_frame_count = false;
|
||||
if (xfbAddr && fbWidth && fbStride && fbHeight)
|
||||
{
|
||||
constexpr int force_safe_texture_cache_hash = 0;
|
||||
// Get the current XFB from texture cache
|
||||
auto* xfb_entry = g_texture_cache->GetXFBTexture(
|
||||
xfbAddr, fbStride, fbHeight, TextureFormat::XFB, force_safe_texture_cache_hash);
|
||||
|
||||
if (xfb_entry && xfb_entry->id != m_last_xfb_id)
|
||||
{
|
||||
m_last_xfb_texture = xfb_entry->texture.get();
|
||||
m_last_xfb_id = xfb_entry->id;
|
||||
m_last_xfb_ticks = ticks;
|
||||
|
||||
auto xfb_rect = xfb_entry->texture->GetConfig().GetRect();
|
||||
xfb_rect.right -= EFBToScaledX(fbStride - fbWidth);
|
||||
|
||||
m_last_xfb_region = xfb_rect;
|
||||
|
||||
// TODO: merge more generic parts into VideoCommon
|
||||
g_renderer->SwapImpl(xfb_entry->texture.get(), xfb_rect, ticks, xfb_entry->gamma);
|
||||
|
||||
m_fps_counter.Update();
|
||||
update_frame_count = true;
|
||||
if (IsFrameDumping())
|
||||
{
|
||||
DoDumpFrame();
|
||||
}
|
||||
}
|
||||
|
||||
// Update our last xfb values
|
||||
m_last_xfb_width = (fbStride < 1 || fbStride > MAX_XFB_WIDTH) ? MAX_XFB_WIDTH : fbStride;
|
||||
m_last_xfb_height = (fbHeight < 1 || fbHeight > MAX_XFB_HEIGHT) ? MAX_XFB_HEIGHT : fbHeight;
|
||||
}
|
||||
|
||||
frameCount++;
|
||||
GFX_DEBUGGER_PAUSE_AT(NEXT_FRAME, true);
|
||||
|
@ -677,9 +681,7 @@ void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const
|
|||
// New frame
|
||||
stats.ResetFrame();
|
||||
|
||||
Core::Callback_VideoCopiedToXFB(m_xfb_written ||
|
||||
(g_ActiveConfig.bUseXFB && g_ActiveConfig.bUseRealXFB));
|
||||
m_xfb_written = false;
|
||||
Core::Callback_VideoCopiedToXFB(update_frame_count);
|
||||
}
|
||||
|
||||
bool Renderer::IsFrameDumping()
|
||||
|
@ -690,10 +692,41 @@ bool Renderer::IsFrameDumping()
|
|||
if (SConfig::GetInstance().m_DumpFrames)
|
||||
return true;
|
||||
|
||||
ShutdownFrameDumping();
|
||||
return false;
|
||||
}
|
||||
|
||||
void Renderer::DoDumpFrame()
|
||||
{
|
||||
UpdateFrameDumpTexture();
|
||||
|
||||
auto result = m_dump_texture->Map();
|
||||
if (result.has_value())
|
||||
{
|
||||
auto raw_data = result.value();
|
||||
DumpFrameData(raw_data.data, raw_data.width, raw_data.height, raw_data.stride,
|
||||
AVIDump::FetchState(m_last_xfb_ticks));
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::UpdateFrameDumpTexture()
|
||||
{
|
||||
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);
|
||||
}
|
||||
m_dump_texture->CopyRectangleFromTexture(m_last_xfb_texture, m_last_xfb_region,
|
||||
EFBRectangle{0, 0, target_width, target_height});
|
||||
}
|
||||
|
||||
void Renderer::ShutdownFrameDumping()
|
||||
{
|
||||
if (!m_frame_dump_thread_running.IsSet())
|
||||
|
@ -704,12 +737,9 @@ void Renderer::ShutdownFrameDumping()
|
|||
m_frame_dump_start.Set();
|
||||
}
|
||||
|
||||
void Renderer::DumpFrameData(const u8* data, int w, int h, int stride, const AVIDump::Frame& state,
|
||||
bool swap_upside_down)
|
||||
void Renderer::DumpFrameData(const u8* data, int w, int h, int stride, const AVIDump::Frame& state)
|
||||
{
|
||||
FinishFrameData();
|
||||
|
||||
m_frame_dump_config = FrameDumpConfig{data, w, h, stride, swap_upside_down, state};
|
||||
m_frame_dump_config = FrameDumpConfig{m_last_xfb_texture, data, w, h, stride, state};
|
||||
|
||||
if (!m_frame_dump_thread_running.IsSet())
|
||||
{
|
||||
|
@ -730,6 +760,7 @@ void Renderer::FinishFrameData()
|
|||
|
||||
m_frame_dump_done.Wait();
|
||||
m_frame_dump_frame_running = false;
|
||||
m_frame_dump_config.texture->Unmap();
|
||||
}
|
||||
|
||||
void Renderer::RunFrameDumps()
|
||||
|
@ -756,12 +787,6 @@ void Renderer::RunFrameDumps()
|
|||
|
||||
auto config = m_frame_dump_config;
|
||||
|
||||
if (config.upside_down)
|
||||
{
|
||||
config.data = config.data + (config.height - 1) * config.stride;
|
||||
config.stride = -config.stride;
|
||||
}
|
||||
|
||||
// Save screenshot
|
||||
if (m_screenshot_request.TestAndClear())
|
||||
{
|
||||
|
|
|
@ -32,6 +32,8 @@
|
|||
#include "VideoCommon/RenderState.h"
|
||||
#include "VideoCommon/VideoCommon.h"
|
||||
|
||||
class AbstractRawTexture;
|
||||
class AbstractTexture;
|
||||
class PostProcessingShaderImplementation;
|
||||
enum class EFBAccessType;
|
||||
|
||||
|
@ -94,7 +96,6 @@ public:
|
|||
float CalculateDrawAspectRatio() const;
|
||||
|
||||
std::tuple<float, float> ScaleToDisplayAspectRatio(int width, int height) const;
|
||||
TargetRectangle CalculateFrameDumpDrawRectangle() const;
|
||||
void UpdateDrawRectangle();
|
||||
|
||||
// Use this to convert a single target rectangle to two stereo rectangles
|
||||
|
@ -130,10 +131,10 @@ public:
|
|||
virtual void BBoxWrite(int index, u16 value) = 0;
|
||||
|
||||
// Finish up the current frame, print some stats
|
||||
void Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const EFBRectangle& rc, u64 ticks,
|
||||
float Gamma = 1.0f);
|
||||
virtual void SwapImpl(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight,
|
||||
const EFBRectangle& rc, u64 ticks, float Gamma = 1.0f) = 0;
|
||||
void Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const EFBRectangle& rc,
|
||||
u64 ticks);
|
||||
virtual void SwapImpl(AbstractTexture* texture, const EFBRectangle& rc, u64 ticks,
|
||||
float Gamma = 1.0f) = 0;
|
||||
|
||||
PEControl::PixelFormat GetPrevPixelFormat() const { return m_prev_efb_format; }
|
||||
void StorePixelFormat(PEControl::PixelFormat new_format) { m_prev_efb_format = new_format; }
|
||||
|
@ -143,6 +144,8 @@ public:
|
|||
virtual void ChangeSurface(void* new_surface_handle) {}
|
||||
bool UseVertexDepthRange() const;
|
||||
|
||||
void ExitFramedumping();
|
||||
|
||||
protected:
|
||||
std::tuple<int, int> CalculateTargetScale(int x, int y) const;
|
||||
bool CalculateTargetSize();
|
||||
|
@ -152,11 +155,6 @@ protected:
|
|||
void CheckFifoRecording();
|
||||
void RecordVideoMemory();
|
||||
|
||||
bool IsFrameDumping();
|
||||
void DumpFrameData(const u8* data, int w, int h, int stride, const AVIDump::Frame& state,
|
||||
bool swap_upside_down = false);
|
||||
void FinishFrameData();
|
||||
|
||||
Common::Flag m_screenshot_request;
|
||||
Common::Event m_screenshot_completed;
|
||||
std::mutex m_screenshot_lock;
|
||||
|
@ -171,7 +169,6 @@ protected:
|
|||
int m_backbuffer_width = 0;
|
||||
int m_backbuffer_height = 0;
|
||||
TargetRectangle m_target_rectangle = {};
|
||||
bool m_xfb_written = false;
|
||||
|
||||
FPSCounter m_fps_counter;
|
||||
|
||||
|
@ -186,8 +183,11 @@ protected:
|
|||
u32 m_last_host_config_bits = 0;
|
||||
|
||||
private:
|
||||
void DoDumpFrame();
|
||||
void RunFrameDumps();
|
||||
void ShutdownFrameDumping();
|
||||
std::tuple<int, int> CalculateOutputDimensions(int width, int height);
|
||||
void UpdateFrameDumpTexture();
|
||||
|
||||
PEControl::PixelFormat m_prev_efb_format = PEControl::INVALID_FMT;
|
||||
unsigned int m_efb_scale = 1;
|
||||
|
@ -205,14 +205,25 @@ private:
|
|||
bool m_frame_dump_frame_running = false;
|
||||
struct FrameDumpConfig
|
||||
{
|
||||
AbstractTexture* texture;
|
||||
const u8* data;
|
||||
int width;
|
||||
int height;
|
||||
int stride;
|
||||
bool upside_down;
|
||||
AVIDump::Frame state;
|
||||
} m_frame_dump_config;
|
||||
|
||||
AbstractTexture* m_last_xfb_texture = nullptr;
|
||||
u64 m_last_xfb_id = std::numeric_limits<u64>::max();
|
||||
u64 m_last_xfb_ticks = 0;
|
||||
EFBRectangle m_last_xfb_region;
|
||||
|
||||
std::unique_ptr<AbstractTexture> m_dump_texture;
|
||||
|
||||
// Note: Only used for auto-ir
|
||||
u32 m_last_xfb_width = MAX_XFB_WIDTH;
|
||||
u32 m_last_xfb_height = MAX_XFB_HEIGHT;
|
||||
|
||||
// NOTE: The methods below are called on the framedumping thread.
|
||||
bool StartFrameDumpToAVI(const FrameDumpConfig& config);
|
||||
void DumpFrameToAVI(const FrameDumpConfig& config);
|
||||
|
@ -220,6 +231,10 @@ private:
|
|||
std::string GetFrameDumpNextImageFileName() const;
|
||||
bool StartFrameDumpToImage(const FrameDumpConfig& config);
|
||||
void DumpFrameToImage(const FrameDumpConfig& config);
|
||||
|
||||
bool IsFrameDumping();
|
||||
void DumpFrameData(const u8* data, int w, int h, int stride, const AVIDump::Frame& state);
|
||||
void FinishFrameData();
|
||||
};
|
||||
|
||||
extern std::unique_ptr<Renderer> g_renderer;
|
||||
|
|
|
@ -158,7 +158,7 @@ void TextureCacheBase::Cleanup(int _frameCount)
|
|||
}
|
||||
else if (_frameCount > TEXTURE_KILL_THRESHOLD + iter->second->frameCount)
|
||||
{
|
||||
if (iter->second->IsEfbCopy())
|
||||
if (iter->second->IsCopy())
|
||||
{
|
||||
// Only remove EFB copies when they wouldn't be used anymore(changed hash), because EFB
|
||||
// copies living on the
|
||||
|
@ -238,11 +238,13 @@ TextureCacheBase::ApplyPaletteToEntry(TCacheEntry* entry, u8* palette, TLUTForma
|
|||
if (!decoded_entry)
|
||||
return nullptr;
|
||||
|
||||
decoded_entry->SetGeneralParameters(entry->addr, entry->size_in_bytes, entry->format);
|
||||
decoded_entry->SetGeneralParameters(entry->addr, entry->size_in_bytes, entry->format,
|
||||
entry->should_force_safe_hashing);
|
||||
decoded_entry->SetDimensions(entry->native_width, entry->native_height, 1);
|
||||
decoded_entry->SetHashes(entry->base_hash, entry->hash);
|
||||
decoded_entry->frameCount = FRAMECOUNT_INVALID;
|
||||
decoded_entry->is_efb_copy = false;
|
||||
decoded_entry->should_force_safe_hashing = false;
|
||||
decoded_entry->SetNotCopy();
|
||||
decoded_entry->may_have_overlapping_textures = entry->may_have_overlapping_textures;
|
||||
|
||||
ConvertTexture(decoded_entry, entry, palette, tlutfmt);
|
||||
|
@ -306,7 +308,7 @@ TextureCacheBase::DoPartialTextureUpdates(TCacheEntry* entry_to_update, u8* pale
|
|||
|
||||
// EFB copies are excluded from these updates, until there's an example where a game would
|
||||
// benefit from updating. This would require more work to be done.
|
||||
if (entry_to_update->IsEfbCopy())
|
||||
if (entry_to_update->IsCopy())
|
||||
return entry_to_update;
|
||||
|
||||
u32 block_width = TexDecoder_GetBlockWidthInTexels(entry_to_update->format.texfmt);
|
||||
|
@ -320,7 +322,7 @@ TextureCacheBase::DoPartialTextureUpdates(TCacheEntry* entry_to_update, u8* pale
|
|||
while (iter.first != iter.second)
|
||||
{
|
||||
TCacheEntry* entry = iter.first->second;
|
||||
if (entry != entry_to_update && entry->IsEfbCopy() && !entry->tmem_only &&
|
||||
if (entry != entry_to_update && entry->IsCopy() && !entry->tmem_only &&
|
||||
entry->references.count(entry_to_update) == 0 &&
|
||||
entry->OverlapsMemoryRange(entry_to_update->addr, entry_to_update->size_in_bytes) &&
|
||||
entry->memory_stride == numBlocksX * block_size)
|
||||
|
@ -462,20 +464,6 @@ static u32 CalculateLevelSize(u32 level_0_size, u32 level)
|
|||
return std::max(level_0_size >> level, 1u);
|
||||
}
|
||||
|
||||
// Used by TextureCacheBase::Load
|
||||
TextureCacheBase::TCacheEntry* TextureCacheBase::ReturnEntry(unsigned int stage, TCacheEntry* entry)
|
||||
{
|
||||
entry->frameCount = FRAMECOUNT_INVALID;
|
||||
bound_textures[stage] = entry;
|
||||
|
||||
GFX_DEBUGGER_PAUSE_AT(NEXT_TEXTURE_CHANGE, true);
|
||||
|
||||
// We need to keep track of invalided textures until they have actually been replaced or re-loaded
|
||||
valid_bind_points.set(stage);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
void TextureCacheBase::BindTextures()
|
||||
{
|
||||
for (size_t i = 0; i < bound_textures.size(); ++i)
|
||||
|
@ -625,7 +613,7 @@ TextureCacheBase::TCacheEntry* TextureCacheBase::Load(const u32 stage)
|
|||
// if this stage was not invalidated by changes to texture registers, keep the current texture
|
||||
if (IsValidBindPoint(stage) && bound_textures[stage])
|
||||
{
|
||||
return ReturnEntry(stage, bound_textures[stage]);
|
||||
return bound_textures[stage];
|
||||
}
|
||||
|
||||
const FourTexUnits& tex = bpmem.tex[stage >> 2];
|
||||
|
@ -639,7 +627,34 @@ TextureCacheBase::TCacheEntry* TextureCacheBase::Load(const u32 stage)
|
|||
const bool use_mipmaps = SamplerCommon::AreBpTexMode0MipmapsEnabled(tex.texMode0[id]);
|
||||
u32 tex_levels = use_mipmaps ? ((tex.texMode1[id].max_lod + 0xf) / 0x10 + 1) : 1;
|
||||
const bool from_tmem = tex.texImage1[id].image_type != 0;
|
||||
const u32 tmem_address_even = from_tmem ? tex.texImage1[id].tmem_even * TMEM_LINE_SIZE : 0;
|
||||
const u32 tmem_address_odd = from_tmem ? tex.texImage2[id].tmem_odd * TMEM_LINE_SIZE : 0;
|
||||
|
||||
auto entry = GetTexture(address, width, height, texformat,
|
||||
g_ActiveConfig.iSafeTextureCache_ColorSamples, tlutaddr, tlutfmt,
|
||||
use_mipmaps, tex_levels, from_tmem, tmem_address_even, tmem_address_odd);
|
||||
|
||||
if (!entry)
|
||||
return nullptr;
|
||||
|
||||
entry->frameCount = FRAMECOUNT_INVALID;
|
||||
bound_textures[stage] = entry;
|
||||
|
||||
GFX_DEBUGGER_PAUSE_AT(NEXT_TEXTURE_CHANGE, true);
|
||||
|
||||
// We need to keep track of invalided textures until they have actually been replaced or
|
||||
// re-loaded
|
||||
valid_bind_points.set(stage);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
TextureCacheBase::TCacheEntry*
|
||||
TextureCacheBase::GetTexture(u32 address, u32 width, u32 height, const TextureFormat texformat,
|
||||
const int textureCacheSafetyColorSampleSize, u32 tlutaddr,
|
||||
TLUTFormat tlutfmt, bool use_mipmaps, u32 tex_levels, bool from_tmem,
|
||||
u32 tmem_address_even, u32 tmem_address_odd)
|
||||
{
|
||||
// TexelSizeInNibbles(format) * width * height / 16;
|
||||
const unsigned int bsw = TexDecoder_GetBlockWidthInTexels(texformat);
|
||||
const unsigned int bsh = TexDecoder_GetBlockHeightInTexels(texformat);
|
||||
|
@ -683,9 +698,12 @@ TextureCacheBase::TCacheEntry* TextureCacheBase::Load(const u32 stage)
|
|||
TexDecoder_GetTextureSizeInBytes(expanded_mip_width, expanded_mip_height, texformat);
|
||||
}
|
||||
|
||||
// TODO: the texture cache lookup is based on address, but a texture from tmem has no reason
|
||||
// to have a unique and valid address. This could result in a regular texture and a tmem
|
||||
// texture aliasing onto the same texture cache entry.
|
||||
const u8* src_data;
|
||||
if (from_tmem)
|
||||
src_data = &texMem[bpmem.tex[stage / 4].texImage1[stage % 4].tmem_even * TMEM_LINE_SIZE];
|
||||
src_data = &texMem[tmem_address_even];
|
||||
else
|
||||
src_data = Memory::GetPointer(address);
|
||||
|
||||
|
@ -704,13 +722,13 @@ TextureCacheBase::TCacheEntry* TextureCacheBase::Load(const u32 stage)
|
|||
|
||||
// TODO: This doesn't hash GB tiles for preloaded RGBA8 textures (instead, it's hashing more data
|
||||
// from the low tmem bank than it should)
|
||||
base_hash = GetHash64(src_data, texture_size, g_ActiveConfig.iSafeTextureCache_ColorSamples);
|
||||
base_hash = GetHash64(src_data, texture_size, textureCacheSafetyColorSampleSize);
|
||||
u32 palette_size = 0;
|
||||
if (isPaletteTexture)
|
||||
{
|
||||
palette_size = TexDecoder_GetPaletteSize(texformat);
|
||||
full_hash = base_hash ^ GetHash64(&texMem[tlutaddr], palette_size,
|
||||
g_ActiveConfig.iSafeTextureCache_ColorSamples);
|
||||
full_hash =
|
||||
base_hash ^ GetHash64(&texMem[tlutaddr], palette_size, textureCacheSafetyColorSampleSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -789,7 +807,7 @@ TextureCacheBase::TCacheEntry* TextureCacheBase::Load(const u32 stage)
|
|||
// texture formats. I'm not sure what effect checking width/height/levels
|
||||
// would have.
|
||||
if (!isPaletteTexture || !g_Config.backend_info.bSupportsPaletteConversion)
|
||||
return ReturnEntry(stage, entry);
|
||||
return entry;
|
||||
|
||||
// Note that we found an unconverted EFB copy, then continue. We'll
|
||||
// perform the conversion later. Currently, we only convert EFB copies to
|
||||
|
@ -816,7 +834,7 @@ TextureCacheBase::TCacheEntry* TextureCacheBase::Load(const u32 stage)
|
|||
{
|
||||
entry = DoPartialTextureUpdates(iter->second, &texMem[tlutaddr], tlutfmt);
|
||||
|
||||
return ReturnEntry(stage, entry);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -841,7 +859,7 @@ TextureCacheBase::TCacheEntry* TextureCacheBase::Load(const u32 stage)
|
|||
|
||||
if (decoded_entry)
|
||||
{
|
||||
return ReturnEntry(stage, decoded_entry);
|
||||
return decoded_entry;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -851,9 +869,8 @@ TextureCacheBase::TCacheEntry* TextureCacheBase::Load(const u32 stage)
|
|||
// textures cause unnecessary slowdowns
|
||||
// Example: Tales of Symphonia (GC) uses over 500 small textures in menus, but only around 70
|
||||
// different ones
|
||||
if (g_ActiveConfig.iSafeTextureCache_ColorSamples == 0 ||
|
||||
std::max(texture_size, palette_size) <=
|
||||
(u32)g_ActiveConfig.iSafeTextureCache_ColorSamples * 8)
|
||||
if (textureCacheSafetyColorSampleSize == 0 ||
|
||||
std::max(texture_size, palette_size) <= (u32)textureCacheSafetyColorSampleSize * 8)
|
||||
{
|
||||
auto hash_range = textures_by_hash.equal_range(full_hash);
|
||||
TexHashCache::iterator hash_iter = hash_range.first;
|
||||
|
@ -866,7 +883,7 @@ TextureCacheBase::TCacheEntry* TextureCacheBase::Load(const u32 stage)
|
|||
{
|
||||
entry = DoPartialTextureUpdates(hash_iter->second, &texMem[tlutaddr], tlutfmt);
|
||||
|
||||
return ReturnEntry(stage, entry);
|
||||
return entry;
|
||||
}
|
||||
++hash_iter;
|
||||
}
|
||||
|
@ -936,69 +953,70 @@ TextureCacheBase::TCacheEntry* TextureCacheBase::Load(const u32 stage)
|
|||
// Initialized to null because only software loading uses this buffer
|
||||
u8* dst_buffer = nullptr;
|
||||
|
||||
if (!hires_tex && decode_on_gpu)
|
||||
if (!hires_tex)
|
||||
{
|
||||
u32 row_stride = bytes_per_block * (expandedWidth / bsw);
|
||||
g_texture_cache->DecodeTextureOnGPU(entry, 0, src_data, texture_size, texformat, width, height,
|
||||
expandedWidth, expandedHeight, row_stride, tlut, tlutfmt);
|
||||
}
|
||||
else if (!hires_tex)
|
||||
{
|
||||
size_t decoded_texture_size = expandedWidth * sizeof(u32) * expandedHeight;
|
||||
|
||||
// Allocate memory for all levels at once
|
||||
size_t total_texture_size = decoded_texture_size;
|
||||
|
||||
// For the downsample, we need 2 buffers; 1 is 1/4 of the original texture, the other 1/16
|
||||
size_t mip_downsample_buffer_size = decoded_texture_size * 5 / 16;
|
||||
|
||||
size_t prev_level_size = decoded_texture_size;
|
||||
for (u32 i = 1; i < tex_levels; ++i)
|
||||
if (decode_on_gpu)
|
||||
{
|
||||
prev_level_size /= 4;
|
||||
total_texture_size += prev_level_size;
|
||||
}
|
||||
|
||||
// Add space for the downsampling at the end
|
||||
total_texture_size += mip_downsample_buffer_size;
|
||||
|
||||
CheckTempSize(total_texture_size);
|
||||
dst_buffer = temp;
|
||||
|
||||
if (!(texformat == TextureFormat::RGBA8 && from_tmem))
|
||||
{
|
||||
TexDecoder_Decode(dst_buffer, src_data, expandedWidth, expandedHeight, texformat, tlut,
|
||||
tlutfmt);
|
||||
u32 row_stride = bytes_per_block * (expandedWidth / bsw);
|
||||
g_texture_cache->DecodeTextureOnGPU(entry, 0, src_data, texture_size, texformat, width,
|
||||
height, expandedWidth, expandedHeight, row_stride, tlut,
|
||||
tlutfmt);
|
||||
}
|
||||
else
|
||||
{
|
||||
u8* src_data_gb =
|
||||
&texMem[bpmem.tex[stage / 4].texImage2[stage % 4].tmem_odd * TMEM_LINE_SIZE];
|
||||
TexDecoder_DecodeRGBA8FromTmem(dst_buffer, src_data, src_data_gb, expandedWidth,
|
||||
expandedHeight);
|
||||
size_t decoded_texture_size = expandedWidth * sizeof(u32) * expandedHeight;
|
||||
|
||||
// Allocate memory for all levels at once
|
||||
size_t total_texture_size = decoded_texture_size;
|
||||
|
||||
// For the downsample, we need 2 buffers; 1 is 1/4 of the original texture, the other 1/16
|
||||
size_t mip_downsample_buffer_size = decoded_texture_size * 5 / 16;
|
||||
|
||||
size_t prev_level_size = decoded_texture_size;
|
||||
for (u32 i = 1; i < tex_levels; ++i)
|
||||
{
|
||||
prev_level_size /= 4;
|
||||
total_texture_size += prev_level_size;
|
||||
}
|
||||
|
||||
// Add space for the downsampling at the end
|
||||
total_texture_size += mip_downsample_buffer_size;
|
||||
|
||||
CheckTempSize(total_texture_size);
|
||||
dst_buffer = temp;
|
||||
if (!(texformat == TextureFormat::RGBA8 && from_tmem))
|
||||
{
|
||||
TexDecoder_Decode(dst_buffer, src_data, expandedWidth, expandedHeight, texformat, tlut,
|
||||
tlutfmt);
|
||||
}
|
||||
else
|
||||
{
|
||||
u8* src_data_gb = &texMem[tmem_address_odd];
|
||||
TexDecoder_DecodeRGBA8FromTmem(dst_buffer, src_data, src_data_gb, expandedWidth,
|
||||
expandedHeight);
|
||||
}
|
||||
|
||||
entry->texture->Load(0, width, height, expandedWidth, dst_buffer, decoded_texture_size);
|
||||
|
||||
arbitrary_mip_detector.AddLevel(width, height, expandedWidth, dst_buffer);
|
||||
|
||||
dst_buffer += decoded_texture_size;
|
||||
}
|
||||
|
||||
entry->texture->Load(0, width, height, expandedWidth, dst_buffer, decoded_texture_size);
|
||||
|
||||
arbitrary_mip_detector.AddLevel(width, height, expandedWidth, dst_buffer);
|
||||
|
||||
dst_buffer += decoded_texture_size;
|
||||
}
|
||||
|
||||
iter = textures_by_address.emplace(address, entry);
|
||||
if (g_ActiveConfig.iSafeTextureCache_ColorSamples == 0 ||
|
||||
std::max(texture_size, palette_size) <=
|
||||
(u32)g_ActiveConfig.iSafeTextureCache_ColorSamples * 8)
|
||||
if (textureCacheSafetyColorSampleSize == 0 ||
|
||||
std::max(texture_size, palette_size) <= (u32)textureCacheSafetyColorSampleSize * 8)
|
||||
{
|
||||
entry->textures_by_hash_iter = textures_by_hash.emplace(full_hash, entry);
|
||||
}
|
||||
|
||||
entry->SetGeneralParameters(address, texture_size, full_format);
|
||||
entry->SetGeneralParameters(address, texture_size, full_format, false);
|
||||
entry->SetDimensions(nativeW, nativeH, tex_levels);
|
||||
entry->SetHashes(base_hash, full_hash);
|
||||
entry->is_efb_copy = false;
|
||||
entry->is_custom_tex = hires_tex != nullptr;
|
||||
entry->memory_stride = entry->BytesPerRow();
|
||||
entry->SetNotCopy();
|
||||
|
||||
std::string basename = "";
|
||||
if (g_ActiveConfig.bDumpTextures && !hires_tex)
|
||||
|
@ -1025,9 +1043,8 @@ TextureCacheBase::TCacheEntry* TextureCacheBase::Load(const u32 stage)
|
|||
const u8* ptr_odd = nullptr;
|
||||
if (from_tmem)
|
||||
{
|
||||
ptr_even = &texMem[bpmem.tex[stage / 4].texImage1[stage % 4].tmem_even * TMEM_LINE_SIZE +
|
||||
texture_size];
|
||||
ptr_odd = &texMem[bpmem.tex[stage / 4].texImage2[stage % 4].tmem_odd * TMEM_LINE_SIZE];
|
||||
ptr_even = &texMem[tmem_address_even + texture_size];
|
||||
ptr_odd = &texMem[tmem_address_odd];
|
||||
}
|
||||
|
||||
for (u32 level = 1; level != texLevels; ++level)
|
||||
|
@ -1081,13 +1098,422 @@ TextureCacheBase::TCacheEntry* TextureCacheBase::Load(const u32 stage)
|
|||
|
||||
entry = DoPartialTextureUpdates(iter->second, &texMem[tlutaddr], tlutfmt);
|
||||
|
||||
return ReturnEntry(stage, entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
TextureCacheBase::TCacheEntry*
|
||||
TextureCacheBase::GetXFBTexture(u32 address, u32 width, u32 height, TextureFormat tex_format,
|
||||
int texture_cache_safety_color_sample_size)
|
||||
{
|
||||
auto tex_info = ComputeTextureInformation(address, width, height, tex_format,
|
||||
texture_cache_safety_color_sample_size, false, 0, 0, 0,
|
||||
TLUTFormat::IA8, 1);
|
||||
if (!tex_info)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const TextureLookupInformation tex_info_value = tex_info.value();
|
||||
|
||||
TCacheEntry* entry = GetXFBFromCache(tex_info_value);
|
||||
if (entry != nullptr)
|
||||
{
|
||||
return entry;
|
||||
}
|
||||
|
||||
entry = CreateNormalTexture(tex_info.value());
|
||||
|
||||
// XFBs created for the purpose of being a container for textures from memory
|
||||
// or as a container for overlapping textures, never need to be combined
|
||||
// with other textures
|
||||
entry->may_have_overlapping_textures = false;
|
||||
|
||||
// At this point, the XFB wasn't found in cache
|
||||
// this means the address is most likely not pointing at an xfb copy but instead
|
||||
// an area of memory. Let's attempt to stitch all entries in this memory space
|
||||
// together
|
||||
bool loaded_from_overlapping = LoadTextureFromOverlappingTextures(entry, tex_info_value);
|
||||
|
||||
if (!loaded_from_overlapping)
|
||||
{
|
||||
// At this point, the xfb address is truly "bogus"
|
||||
// it likely is an area of memory defined by the CPU
|
||||
// so load it from memory
|
||||
LoadTextureFromMemory(entry, tex_info_value);
|
||||
}
|
||||
|
||||
if (g_ActiveConfig.bDumpXFBTarget)
|
||||
{
|
||||
// While this isn't really an xfb copy, we can treat it as such
|
||||
// for dumping purposes
|
||||
static int xfb_count = 0;
|
||||
const std::string xfb_type = loaded_from_overlapping ? "combined" : "from_memory";
|
||||
entry->texture->Save(StringFromFormat("%sxfb_%s_%i.png",
|
||||
File::GetUserPath(D_DUMPTEXTURES_IDX).c_str(),
|
||||
xfb_type.c_str(), xfb_count++),
|
||||
0);
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
std::optional<TextureLookupInformation> TextureCacheBase::ComputeTextureInformation(
|
||||
u32 address, u32 width, u32 height, TextureFormat tex_format,
|
||||
int texture_cache_safety_color_sample_size, bool from_tmem, u32 tmem_address_even,
|
||||
u32 tmem_address_odd, u32 tlut_address, TLUTFormat tlut_format, u32 levels)
|
||||
{
|
||||
TextureLookupInformation tex_info;
|
||||
|
||||
tex_info.from_tmem = from_tmem;
|
||||
tex_info.tmem_address_even = tmem_address_even;
|
||||
tex_info.tmem_address_odd = tmem_address_odd;
|
||||
|
||||
tex_info.address = address;
|
||||
|
||||
if (from_tmem)
|
||||
tex_info.src_data = &texMem[tex_info.tmem_address_even];
|
||||
else
|
||||
tex_info.src_data = Memory::GetPointer(tex_info.address);
|
||||
|
||||
if (tex_info.src_data == nullptr)
|
||||
{
|
||||
ERROR_LOG(VIDEO, "Trying to use an invalid texture address 0x%8x", tex_info.address);
|
||||
return {};
|
||||
}
|
||||
|
||||
tex_info.texture_cache_safety_color_sample_size = texture_cache_safety_color_sample_size;
|
||||
|
||||
// TexelSizeInNibbles(format) * width * height / 16;
|
||||
tex_info.block_width = TexDecoder_GetBlockWidthInTexels(tex_format);
|
||||
tex_info.block_height = TexDecoder_GetBlockHeightInTexels(tex_format);
|
||||
|
||||
tex_info.bytes_per_block = (tex_info.block_width * tex_info.block_height *
|
||||
TexDecoder_GetTexelSizeInNibbles(tex_format)) /
|
||||
2;
|
||||
|
||||
tex_info.expanded_width = Common::AlignUp(width, tex_info.block_width);
|
||||
tex_info.expanded_height = Common::AlignUp(height, tex_info.block_height);
|
||||
|
||||
tex_info.total_bytes = TexDecoder_GetTextureSizeInBytes(tex_info.expanded_width,
|
||||
tex_info.expanded_height, tex_format);
|
||||
|
||||
tex_info.native_width = width;
|
||||
tex_info.native_height = height;
|
||||
tex_info.native_levels = levels;
|
||||
|
||||
// GPUs don't like when the specified mipmap count would require more than one 1x1-sized LOD in
|
||||
// the mipmap chain
|
||||
// e.g. 64x64 with 7 LODs would have the mipmap chain 64x64,32x32,16x16,8x8,4x4,2x2,1x1,0x0, so we
|
||||
// limit the mipmap count to 6 there
|
||||
tex_info.computed_levels = std::min<u32>(
|
||||
IntLog2(std::max(tex_info.native_width, tex_info.native_height)) + 1, tex_info.native_levels);
|
||||
|
||||
tex_info.full_format = TextureAndTLUTFormat(tex_format, tlut_format);
|
||||
tex_info.tlut_address = tlut_address;
|
||||
|
||||
// TODO: This doesn't hash GB tiles for preloaded RGBA8 textures (instead, it's hashing more data
|
||||
// from the low tmem bank than it should)
|
||||
tex_info.base_hash = GetHash64(tex_info.src_data, tex_info.total_bytes,
|
||||
tex_info.texture_cache_safety_color_sample_size);
|
||||
|
||||
tex_info.is_palette_texture = IsColorIndexed(tex_format);
|
||||
|
||||
if (tex_info.is_palette_texture)
|
||||
{
|
||||
tex_info.palette_size = TexDecoder_GetPaletteSize(tex_format);
|
||||
tex_info.full_hash =
|
||||
tex_info.base_hash ^ GetHash64(&texMem[tex_info.tlut_address], tex_info.palette_size,
|
||||
tex_info.texture_cache_safety_color_sample_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
tex_info.full_hash = tex_info.base_hash;
|
||||
}
|
||||
|
||||
return tex_info;
|
||||
}
|
||||
|
||||
TextureCacheBase::TCacheEntry*
|
||||
TextureCacheBase::GetXFBFromCache(const TextureLookupInformation& tex_info)
|
||||
{
|
||||
auto iter_range = textures_by_address.equal_range(tex_info.address);
|
||||
TexAddrCache::iterator iter = iter_range.first;
|
||||
|
||||
while (iter != iter_range.second)
|
||||
{
|
||||
TCacheEntry* entry = iter->second;
|
||||
|
||||
if ((entry->is_xfb_copy || entry->format.texfmt == TextureFormat::XFB) &&
|
||||
entry->native_width == tex_info.native_width &&
|
||||
static_cast<unsigned int>(entry->native_height * entry->y_scale) ==
|
||||
tex_info.native_height &&
|
||||
entry->memory_stride == entry->BytesPerRow() && !entry->may_have_overlapping_textures)
|
||||
{
|
||||
if (tex_info.base_hash == entry->hash && !entry->reference_changed)
|
||||
{
|
||||
return entry;
|
||||
}
|
||||
else
|
||||
{
|
||||
// At this point, we either have an xfb copy that has changed its hash
|
||||
// or an xfb created by stitching or from memory that has been changed
|
||||
// we are safe to invalidate this
|
||||
iter = InvalidateTexture(iter);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
++iter;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool TextureCacheBase::LoadTextureFromOverlappingTextures(TCacheEntry* entry_to_update,
|
||||
const TextureLookupInformation& tex_info)
|
||||
{
|
||||
bool updated_entry = false;
|
||||
|
||||
u32 numBlocksX = entry_to_update->native_width / tex_info.block_width;
|
||||
|
||||
auto iter = FindOverlappingTextures(entry_to_update->addr, entry_to_update->size_in_bytes);
|
||||
while (iter.first != iter.second)
|
||||
{
|
||||
TCacheEntry* entry = iter.first->second;
|
||||
if (entry != entry_to_update && entry->IsCopy() && !entry->tmem_only &&
|
||||
entry->references.count(entry_to_update) == 0 &&
|
||||
entry->OverlapsMemoryRange(entry_to_update->addr, entry_to_update->size_in_bytes) &&
|
||||
entry->memory_stride == entry_to_update->memory_stride)
|
||||
{
|
||||
if (entry->hash == entry->CalculateHash())
|
||||
{
|
||||
if (tex_info.is_palette_texture)
|
||||
{
|
||||
TCacheEntry* decoded_entry =
|
||||
ApplyPaletteToEntry(entry, nullptr, tex_info.full_format.tlutfmt);
|
||||
if (decoded_entry)
|
||||
{
|
||||
// Link the efb copy with the partially updated texture, so we won't apply this partial
|
||||
// update again
|
||||
entry->CreateReference(entry_to_update);
|
||||
// Mark the texture update as used, as if it was loaded directly
|
||||
entry->frameCount = FRAMECOUNT_INVALID;
|
||||
entry = decoded_entry;
|
||||
}
|
||||
else
|
||||
{
|
||||
++iter.first;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
s32 src_x, src_y, dst_x, dst_y;
|
||||
|
||||
// Note for understanding the math:
|
||||
// Normal textures can't be strided, so the 2 missing cases with src_x > 0 don't exist
|
||||
if (entry->addr >= entry_to_update->addr)
|
||||
{
|
||||
s32 block_offset = (entry->addr - entry_to_update->addr) / tex_info.bytes_per_block;
|
||||
s32 block_x = block_offset % numBlocksX;
|
||||
s32 block_y = block_offset / numBlocksX;
|
||||
src_x = 0;
|
||||
src_y = 0;
|
||||
dst_x = block_x * tex_info.block_width;
|
||||
dst_y = block_y * tex_info.block_height;
|
||||
}
|
||||
else
|
||||
{
|
||||
s32 block_offset = (entry_to_update->addr - entry->addr) / tex_info.bytes_per_block;
|
||||
s32 block_x = block_offset % numBlocksX;
|
||||
s32 block_y = block_offset / numBlocksX;
|
||||
src_x = block_x * tex_info.block_width;
|
||||
src_y = block_y * tex_info.block_height;
|
||||
dst_x = 0;
|
||||
dst_y = 0;
|
||||
}
|
||||
|
||||
u32 copy_width =
|
||||
std::min(entry->native_width - src_x, entry_to_update->native_width - dst_x);
|
||||
u32 copy_height =
|
||||
std::min((entry->native_height * entry->y_scale) - src_y,
|
||||
(entry_to_update->native_height * entry_to_update->y_scale) - dst_y);
|
||||
|
||||
// If one of the textures is scaled, scale both with the current efb scaling factor
|
||||
if (entry_to_update->native_width != entry_to_update->GetWidth() ||
|
||||
(entry_to_update->native_height * entry_to_update->y_scale) !=
|
||||
entry_to_update->GetHeight() ||
|
||||
entry->native_width != entry->GetWidth() ||
|
||||
(entry->native_height * entry->y_scale) != entry->GetHeight())
|
||||
{
|
||||
ScaleTextureCacheEntryTo(
|
||||
entry_to_update, g_renderer->EFBToScaledX(entry_to_update->native_width),
|
||||
g_renderer->EFBToScaledY(entry_to_update->native_height * entry_to_update->y_scale));
|
||||
ScaleTextureCacheEntryTo(entry, g_renderer->EFBToScaledX(entry->native_width),
|
||||
g_renderer->EFBToScaledY(entry->native_height * entry->y_scale));
|
||||
|
||||
src_x = g_renderer->EFBToScaledX(src_x);
|
||||
src_y = g_renderer->EFBToScaledY(src_y);
|
||||
dst_x = g_renderer->EFBToScaledX(dst_x);
|
||||
dst_y = g_renderer->EFBToScaledY(dst_y);
|
||||
copy_width = g_renderer->EFBToScaledX(copy_width);
|
||||
copy_height = g_renderer->EFBToScaledY(copy_height);
|
||||
}
|
||||
|
||||
MathUtil::Rectangle<int> srcrect, dstrect;
|
||||
srcrect.left = src_x;
|
||||
srcrect.top = src_y;
|
||||
srcrect.right = (src_x + copy_width);
|
||||
srcrect.bottom = (src_y + copy_height);
|
||||
|
||||
if (static_cast<int>(entry->GetWidth()) == srcrect.GetWidth())
|
||||
{
|
||||
srcrect.right -= 1;
|
||||
}
|
||||
|
||||
if (static_cast<int>(entry->GetHeight()) == srcrect.GetHeight())
|
||||
{
|
||||
srcrect.bottom -= 1;
|
||||
}
|
||||
|
||||
dstrect.left = dst_x;
|
||||
dstrect.top = dst_y;
|
||||
dstrect.right = (dst_x + copy_width);
|
||||
dstrect.bottom = (dst_y + copy_height);
|
||||
|
||||
if (static_cast<int>(entry_to_update->GetWidth()) == dstrect.GetWidth())
|
||||
{
|
||||
dstrect.right -= 1;
|
||||
}
|
||||
|
||||
if (static_cast<int>(entry_to_update->GetHeight()) == dstrect.GetHeight())
|
||||
{
|
||||
dstrect.bottom -= 1;
|
||||
}
|
||||
|
||||
entry_to_update->texture->CopyRectangleFromTexture(entry->texture.get(), srcrect, dstrect);
|
||||
|
||||
updated_entry = true;
|
||||
|
||||
if (tex_info.is_palette_texture)
|
||||
{
|
||||
// Remove the temporary converted texture, it won't be used anywhere else
|
||||
// TODO: It would be nice to convert and copy in one step, but this code path isn't common
|
||||
InvalidateTexture(GetTexCacheIter(entry));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Link the two textures together, so we won't apply this partial update again
|
||||
entry->CreateReference(entry_to_update);
|
||||
// Mark the texture update as used, as if it was loaded directly
|
||||
entry->frameCount = FRAMECOUNT_INVALID;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the hash does not match, this EFB copy will not be used for anything, so remove it
|
||||
iter.first = InvalidateTexture(iter.first);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
++iter.first;
|
||||
}
|
||||
|
||||
return updated_entry;
|
||||
}
|
||||
|
||||
TextureCacheBase::TCacheEntry*
|
||||
TextureCacheBase::CreateNormalTexture(const TextureLookupInformation& tex_info)
|
||||
{
|
||||
// create the entry/texture
|
||||
TextureConfig config;
|
||||
config.width = tex_info.native_width;
|
||||
config.height = tex_info.native_height;
|
||||
config.levels = tex_info.computed_levels;
|
||||
config.format = AbstractTextureFormat::RGBA8;
|
||||
config.rendertarget = true;
|
||||
|
||||
TCacheEntry* entry = AllocateCacheEntry(config);
|
||||
GFX_DEBUGGER_PAUSE_AT(NEXT_NEW_TEXTURE, true);
|
||||
|
||||
if (!entry)
|
||||
return nullptr;
|
||||
|
||||
textures_by_address.emplace(tex_info.address, entry);
|
||||
if (tex_info.texture_cache_safety_color_sample_size == 0 ||
|
||||
std::max(tex_info.total_bytes, tex_info.palette_size) <=
|
||||
(u32)tex_info.texture_cache_safety_color_sample_size * 8)
|
||||
{
|
||||
entry->textures_by_hash_iter = textures_by_hash.emplace(tex_info.full_hash, entry);
|
||||
}
|
||||
|
||||
entry->SetGeneralParameters(tex_info.address, tex_info.total_bytes, tex_info.full_format, false);
|
||||
entry->SetDimensions(tex_info.native_width, tex_info.native_height, tex_info.computed_levels);
|
||||
entry->SetHashes(tex_info.base_hash, tex_info.full_hash);
|
||||
entry->is_custom_tex = false;
|
||||
entry->memory_stride = entry->BytesPerRow();
|
||||
entry->SetNotCopy();
|
||||
|
||||
INCSTAT(stats.numTexturesUploaded);
|
||||
SETSTAT(stats.numTexturesAlive, textures_by_address.size());
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
void TextureCacheBase::LoadTextureFromMemory(TCacheEntry* entry_to_update,
|
||||
const TextureLookupInformation& tex_info)
|
||||
{
|
||||
// We can decode on the GPU if it is a supported format and the flag is enabled.
|
||||
// Currently we don't decode RGBA8 textures from Tmem, as that would require copying from both
|
||||
// banks, and if we're doing an copy we may as well just do the whole thing on the CPU, since
|
||||
// there's no conversion between formats. In the future this could be extended with a separate
|
||||
// shader, however.
|
||||
bool decode_on_gpu = g_ActiveConfig.UseGPUTextureDecoding() &&
|
||||
g_texture_cache->SupportsGPUTextureDecode(tex_info.full_format.texfmt,
|
||||
tex_info.full_format.tlutfmt) &&
|
||||
!(tex_info.from_tmem && tex_info.full_format.texfmt == TextureFormat::RGBA8);
|
||||
|
||||
LoadTextureLevelZeroFromMemory(entry_to_update, tex_info, decode_on_gpu);
|
||||
}
|
||||
|
||||
void TextureCacheBase::LoadTextureLevelZeroFromMemory(TCacheEntry* entry_to_update,
|
||||
const TextureLookupInformation& tex_info,
|
||||
bool decode_on_gpu)
|
||||
{
|
||||
const u8* tlut = &texMem[tex_info.tlut_address];
|
||||
|
||||
if (decode_on_gpu)
|
||||
{
|
||||
u32 row_stride = tex_info.bytes_per_block * (tex_info.expanded_width / tex_info.block_width);
|
||||
g_texture_cache->DecodeTextureOnGPU(
|
||||
entry_to_update, 0, tex_info.src_data, tex_info.total_bytes, tex_info.full_format.texfmt,
|
||||
tex_info.native_width, tex_info.native_height, tex_info.expanded_width,
|
||||
tex_info.expanded_height, row_stride, tlut, tex_info.full_format.tlutfmt);
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t decoded_texture_size = tex_info.expanded_width * sizeof(u32) * tex_info.expanded_height;
|
||||
CheckTempSize(decoded_texture_size);
|
||||
if (!(tex_info.full_format.texfmt == TextureFormat::RGBA8 && tex_info.from_tmem))
|
||||
{
|
||||
TexDecoder_Decode(temp, tex_info.src_data, tex_info.expanded_width, tex_info.expanded_height,
|
||||
tex_info.full_format.texfmt, tlut, tex_info.full_format.tlutfmt);
|
||||
}
|
||||
else
|
||||
{
|
||||
u8* src_data_gb = &texMem[tex_info.tmem_address_odd];
|
||||
TexDecoder_DecodeRGBA8FromTmem(temp, tex_info.src_data, src_data_gb, tex_info.expanded_width,
|
||||
tex_info.expanded_height);
|
||||
}
|
||||
|
||||
entry_to_update->texture->Load(0, tex_info.native_width, tex_info.native_height,
|
||||
tex_info.expanded_width, temp, decoded_texture_size);
|
||||
}
|
||||
}
|
||||
|
||||
void TextureCacheBase::CopyRenderTargetToTexture(u32 dstAddr, EFBCopyFormat dstFormat,
|
||||
u32 dstStride, bool is_depth_copy,
|
||||
const EFBRectangle& srcRect, bool isIntensity,
|
||||
bool scaleByHalf)
|
||||
bool scaleByHalf, float y_scale, float gamma)
|
||||
{
|
||||
// Emulation methods:
|
||||
//
|
||||
|
@ -1160,6 +1586,11 @@ void TextureCacheBase::CopyRenderTargetToTexture(u32 dstAddr, EFBCopyFormat dstF
|
|||
PEControl::PixelFormat srcFormat = bpmem.zcontrol.pixel_format;
|
||||
bool efbHasAlpha = srcFormat == PEControl::RGBA6_Z24;
|
||||
|
||||
bool copy_to_ram =
|
||||
!g_ActiveConfig.bSkipEFBCopyToRam || g_ActiveConfig.backend_info.bForceCopyToRam;
|
||||
bool copy_to_vram = g_ActiveConfig.backend_info.bSupportsCopyToVram;
|
||||
bool is_xfb_copy = false;
|
||||
|
||||
if (is_depth_copy)
|
||||
{
|
||||
switch (dstFormat)
|
||||
|
@ -1388,6 +1819,16 @@ void TextureCacheBase::CopyRenderTargetToTexture(u32 dstAddr, EFBCopyFormat dstF
|
|||
}
|
||||
break;
|
||||
|
||||
case EFBCopyFormat::XFB: // XFB copy, we just pretend it's an RGBX copy
|
||||
colmat[0] = colmat[5] = colmat[10] = colmat[15] = 1.0f;
|
||||
ColorMask[3] = 0.0f;
|
||||
fConstAdd[3] = 1.0f;
|
||||
cbufid = 30; // just re-use the RGBX8 cbufid from above
|
||||
copy_to_ram =
|
||||
!g_ActiveConfig.bSkipXFBCopyToRam || g_ActiveConfig.backend_info.bForceCopyToRam;
|
||||
is_xfb_copy = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
ERROR_LOG(VIDEO, "Unknown copy color format: 0x%X", static_cast<int>(dstFormat));
|
||||
colmat[0] = colmat[5] = colmat[10] = colmat[15] = 1.0f;
|
||||
|
@ -1418,7 +1859,7 @@ void TextureCacheBase::CopyRenderTargetToTexture(u32 dstAddr, EFBCopyFormat dstF
|
|||
const u32 blockW = TexDecoder_GetBlockWidthInTexels(baseFormat);
|
||||
|
||||
// Round up source height to multiple of block size
|
||||
u32 actualHeight = Common::AlignUp(tex_h, blockH);
|
||||
u32 actualHeight = Common::AlignUp(static_cast<unsigned int>(tex_h * y_scale), blockH);
|
||||
const u32 actualWidth = Common::AlignUp(tex_w, blockW);
|
||||
|
||||
u32 num_blocks_y = actualHeight / blockH;
|
||||
|
@ -1430,24 +1871,28 @@ void TextureCacheBase::CopyRenderTargetToTexture(u32 dstAddr, EFBCopyFormat dstF
|
|||
const u32 bytes_per_row = num_blocks_x * bytes_per_block;
|
||||
const u32 covered_range = num_blocks_y * dstStride;
|
||||
|
||||
bool copy_to_ram = !g_ActiveConfig.bSkipEFBCopyToRam;
|
||||
bool copy_to_vram = true;
|
||||
|
||||
if (copy_to_ram)
|
||||
{
|
||||
EFBCopyParams format(srcFormat, dstFormat, is_depth_copy, isIntensity);
|
||||
EFBCopyParams format(srcFormat, dstFormat, is_depth_copy, isIntensity, y_scale);
|
||||
CopyEFB(dst, format, tex_w, bytes_per_row, num_blocks_y, dstStride, srcRect, scaleByHalf);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Hack: Most games don't actually need the correct texture data in RAM
|
||||
// and we can just keep a copy in VRAM. We zero the memory so we
|
||||
// can check it hasn't changed before using our copy in VRAM.
|
||||
u8* ptr = dst;
|
||||
for (u32 i = 0; i < num_blocks_y; i++)
|
||||
if (is_xfb_copy)
|
||||
{
|
||||
memset(ptr, 0, bytes_per_row);
|
||||
ptr += dstStride;
|
||||
UninitializeXFBMemory(dst, dstStride, bytes_per_row, num_blocks_y);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Hack: Most games don't actually need the correct texture data in RAM
|
||||
// and we can just keep a copy in VRAM. We zero the memory so we
|
||||
// can check it hasn't changed before using our copy in VRAM.
|
||||
u8* ptr = dst;
|
||||
for (u32 i = 0; i < num_blocks_y; i++)
|
||||
{
|
||||
memset(ptr, 0, bytes_per_row);
|
||||
ptr += dstStride;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1487,6 +1932,15 @@ void TextureCacheBase::CopyRenderTargetToTexture(u32 dstAddr, EFBCopyFormat dstF
|
|||
while (iter.first != iter.second)
|
||||
{
|
||||
TCacheEntry* entry = iter.first->second;
|
||||
|
||||
if (entry->addr == dstAddr && entry->is_xfb_copy)
|
||||
{
|
||||
for (auto& reference : entry->references)
|
||||
{
|
||||
reference->reference_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (entry->OverlapsMemoryRange(dstAddr, covered_range))
|
||||
{
|
||||
u32 overlap_range = std::min(entry->addr + entry->size_in_bytes, dstAddr + covered_range) -
|
||||
|
@ -1500,6 +1954,19 @@ void TextureCacheBase::CopyRenderTargetToTexture(u32 dstAddr, EFBCopyFormat dstF
|
|||
}
|
||||
entry->may_have_overlapping_textures = true;
|
||||
|
||||
// There are cases (Rogue Squadron 2 / Texas Holdem on Wiiware) where
|
||||
// for xfb copies the textures overlap which causes the hash of the first copy
|
||||
// to be different (from when it was originally created). This has no implications
|
||||
// for XFB2Tex because the underlying memory doesn't change (dummy values) but
|
||||
// can affect XFB2Ram when we compare the texture cache copy hash with the
|
||||
// newly computed hash
|
||||
// By calculating the hash when we receive overlapping xfbs, we are able
|
||||
// to mitigate this
|
||||
if (entry->is_xfb_copy && copy_to_ram)
|
||||
{
|
||||
entry->hash = entry->CalculateHash();
|
||||
}
|
||||
|
||||
// Do not load textures by hash, if they were at least partly overwritten by an efb copy.
|
||||
// In this case, comparing the hash is not enough to check, if two textures are identical.
|
||||
if (entry->textures_by_hash_iter != textures_by_hash.end())
|
||||
|
@ -1524,11 +1991,21 @@ void TextureCacheBase::CopyRenderTargetToTexture(u32 dstAddr, EFBCopyFormat dstF
|
|||
|
||||
if (entry)
|
||||
{
|
||||
entry->SetGeneralParameters(dstAddr, 0, baseFormat);
|
||||
entry->SetGeneralParameters(dstAddr, 0, baseFormat, is_xfb_copy);
|
||||
entry->SetDimensions(tex_w, tex_h, 1);
|
||||
entry->y_scale = y_scale;
|
||||
entry->gamma = gamma;
|
||||
|
||||
entry->frameCount = FRAMECOUNT_INVALID;
|
||||
entry->SetEfbCopy(dstStride);
|
||||
if (is_xfb_copy)
|
||||
{
|
||||
entry->should_force_safe_hashing = is_xfb_copy;
|
||||
entry->SetXfbCopy(dstStride);
|
||||
}
|
||||
else
|
||||
{
|
||||
entry->SetEfbCopy(dstStride);
|
||||
}
|
||||
entry->may_have_overlapping_textures = false;
|
||||
entry->is_custom_tex = false;
|
||||
|
||||
|
@ -1537,12 +2014,21 @@ void TextureCacheBase::CopyRenderTargetToTexture(u32 dstAddr, EFBCopyFormat dstF
|
|||
u64 hash = entry->CalculateHash();
|
||||
entry->SetHashes(hash, hash);
|
||||
|
||||
if (g_ActiveConfig.bDumpEFBTarget)
|
||||
if (g_ActiveConfig.bDumpEFBTarget && !is_xfb_copy)
|
||||
{
|
||||
static int count = 0;
|
||||
static int efb_count = 0;
|
||||
entry->texture->Save(StringFromFormat("%sefb_frame_%i.png",
|
||||
File::GetUserPath(D_DUMPTEXTURES_IDX).c_str(),
|
||||
count++),
|
||||
efb_count++),
|
||||
0);
|
||||
}
|
||||
|
||||
if (g_ActiveConfig.bDumpXFBTarget && is_xfb_copy)
|
||||
{
|
||||
static int xfb_count = 0;
|
||||
entry->texture->Save(StringFromFormat("%sxfb_copy_%i.png",
|
||||
File::GetUserPath(D_DUMPTEXTURES_IDX).c_str(),
|
||||
xfb_count++),
|
||||
0);
|
||||
}
|
||||
|
||||
|
@ -1551,6 +2037,33 @@ void TextureCacheBase::CopyRenderTargetToTexture(u32 dstAddr, EFBCopyFormat dstF
|
|||
}
|
||||
}
|
||||
|
||||
void TextureCacheBase::UninitializeXFBMemory(u8* dst, u32 stride, u32 bytes_per_row,
|
||||
u32 num_blocks_y)
|
||||
{
|
||||
// Originally, we planned on using a 'key color'
|
||||
// for alpha to address partial xfbs (Mario Strikers / Chicken Little).
|
||||
// This work was removed since it was unfinished but there
|
||||
// was still a desire to differentiate between the old and the new approach
|
||||
// which is why we still set uninitialized xfb memory to fuchsia
|
||||
// (Y=1,U=254,V=254) instead of dark green (Y=0,U=0,V=0) in YUV
|
||||
// like is done in the EFB path.
|
||||
for (u32 i = 0; i < num_blocks_y; i++)
|
||||
{
|
||||
for (u32 offset = 0; offset < bytes_per_row; offset++)
|
||||
{
|
||||
if (offset % 2)
|
||||
{
|
||||
dst[offset] = 254;
|
||||
}
|
||||
else
|
||||
{
|
||||
dst[offset] = 1;
|
||||
}
|
||||
}
|
||||
dst += stride;
|
||||
}
|
||||
}
|
||||
|
||||
TextureCacheBase::TCacheEntry* TextureCacheBase::AllocateCacheEntry(const TextureConfig& config)
|
||||
{
|
||||
std::unique_ptr<AbstractTexture> texture = AllocateTexture(config);
|
||||
|
@ -1561,6 +2074,7 @@ TextureCacheBase::TCacheEntry* TextureCacheBase::AllocateCacheEntry(const Textur
|
|||
}
|
||||
TCacheEntry* cacheEntry = new TCacheEntry(std::move(texture));
|
||||
cacheEntry->textures_by_hash_iter = textures_by_hash.end();
|
||||
cacheEntry->id = last_entry_id++;
|
||||
return cacheEntry;
|
||||
}
|
||||
|
||||
|
@ -1684,14 +2198,15 @@ u32 TextureCacheBase::TCacheEntry::NumBlocksY() const
|
|||
{
|
||||
u32 blockH = TexDecoder_GetBlockHeightInTexels(format.texfmt);
|
||||
// Round up source height to multiple of block size
|
||||
u32 actualHeight = Common::AlignUp(native_height, blockH);
|
||||
u32 actualHeight = Common::AlignUp(static_cast<unsigned int>(native_height * y_scale), blockH);
|
||||
|
||||
return actualHeight / blockH;
|
||||
}
|
||||
|
||||
void TextureCacheBase::TCacheEntry::SetEfbCopy(u32 stride)
|
||||
void TextureCacheBase::TCacheEntry::SetXfbCopy(u32 stride)
|
||||
{
|
||||
is_efb_copy = true;
|
||||
is_efb_copy = false;
|
||||
is_xfb_copy = true;
|
||||
memory_stride = stride;
|
||||
|
||||
_assert_msg_(VIDEO, memory_stride >= BytesPerRow(), "Memory stride is too small");
|
||||
|
@ -1699,12 +2214,39 @@ void TextureCacheBase::TCacheEntry::SetEfbCopy(u32 stride)
|
|||
size_in_bytes = memory_stride * NumBlocksY();
|
||||
}
|
||||
|
||||
void TextureCacheBase::TCacheEntry::SetEfbCopy(u32 stride)
|
||||
{
|
||||
is_efb_copy = true;
|
||||
is_xfb_copy = false;
|
||||
memory_stride = stride;
|
||||
|
||||
_assert_msg_(VIDEO, memory_stride >= BytesPerRow(), "Memory stride is too small");
|
||||
|
||||
size_in_bytes = memory_stride * NumBlocksY();
|
||||
}
|
||||
|
||||
void TextureCacheBase::TCacheEntry::SetNotCopy()
|
||||
{
|
||||
is_xfb_copy = false;
|
||||
is_efb_copy = false;
|
||||
}
|
||||
|
||||
int TextureCacheBase::TCacheEntry::HashSampleSize() const
|
||||
{
|
||||
if (should_force_safe_hashing)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return g_ActiveConfig.iSafeTextureCache_ColorSamples;
|
||||
}
|
||||
|
||||
u64 TextureCacheBase::TCacheEntry::CalculateHash() const
|
||||
{
|
||||
u8* ptr = Memory::GetPointer(addr);
|
||||
if (memory_stride == BytesPerRow())
|
||||
{
|
||||
return GetHash64(ptr, size_in_bytes, g_ActiveConfig.iSafeTextureCache_ColorSamples);
|
||||
return GetHash64(ptr, size_in_bytes, HashSampleSize());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1712,11 +2254,11 @@ u64 TextureCacheBase::TCacheEntry::CalculateHash() const
|
|||
u64 temp_hash = size_in_bytes;
|
||||
|
||||
u32 samples_per_row = 0;
|
||||
if (g_ActiveConfig.iSafeTextureCache_ColorSamples != 0)
|
||||
if (HashSampleSize() != 0)
|
||||
{
|
||||
// Hash at least 4 samples per row to avoid hashing in a bad pattern, like just on the left
|
||||
// side of the efb copy
|
||||
samples_per_row = std::max(g_ActiveConfig.iSafeTextureCache_ColorSamples / blocks, 4u);
|
||||
samples_per_row = std::max(HashSampleSize() / blocks, 4u);
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < blocks; i++)
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
#include <bitset>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
@ -45,21 +47,59 @@ struct TextureAndTLUTFormat
|
|||
struct EFBCopyParams
|
||||
{
|
||||
EFBCopyParams(PEControl::PixelFormat efb_format_, EFBCopyFormat copy_format_, bool depth_,
|
||||
bool yuv_)
|
||||
: efb_format(efb_format_), copy_format(copy_format_), depth(depth_), yuv(yuv_)
|
||||
bool yuv_, float y_scale_)
|
||||
: efb_format(efb_format_), copy_format(copy_format_), depth(depth_), yuv(yuv_),
|
||||
y_scale(y_scale_)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator<(const EFBCopyParams& rhs) const
|
||||
{
|
||||
return std::tie(efb_format, copy_format, depth, yuv) <
|
||||
std::tie(rhs.efb_format, rhs.copy_format, rhs.depth, rhs.yuv);
|
||||
return std::tie(efb_format, copy_format, depth, yuv, y_scale) <
|
||||
std::tie(rhs.efb_format, rhs.copy_format, rhs.depth, rhs.yuv, rhs.y_scale);
|
||||
}
|
||||
|
||||
PEControl::PixelFormat efb_format;
|
||||
EFBCopyFormat copy_format;
|
||||
bool depth;
|
||||
bool yuv;
|
||||
float y_scale;
|
||||
};
|
||||
|
||||
struct TextureLookupInformation
|
||||
{
|
||||
u32 address;
|
||||
|
||||
u32 block_width;
|
||||
u32 block_height;
|
||||
u32 bytes_per_block;
|
||||
|
||||
u32 expanded_width;
|
||||
u32 expanded_height;
|
||||
u32 native_width;
|
||||
u32 native_height;
|
||||
u32 total_bytes;
|
||||
u32 native_levels = 1;
|
||||
u32 computed_levels;
|
||||
|
||||
u64 base_hash;
|
||||
u64 full_hash;
|
||||
|
||||
TextureAndTLUTFormat full_format;
|
||||
u32 tlut_address = 0;
|
||||
|
||||
bool is_palette_texture = false;
|
||||
u32 palette_size = 0;
|
||||
|
||||
bool use_mipmaps = false;
|
||||
|
||||
bool from_tmem = false;
|
||||
u32 tmem_address_even = 0;
|
||||
u32 tmem_address_odd = 0;
|
||||
|
||||
int texture_cache_safety_color_sample_size = 0; // Default to safe hashing
|
||||
|
||||
u8* src_data;
|
||||
};
|
||||
|
||||
class TextureCacheBase
|
||||
|
@ -84,6 +124,13 @@ public:
|
|||
bool tmem_only = false; // indicates that this texture only exists in the tmem cache
|
||||
bool has_arbitrary_mips = false; // indicates that the mips in this texture are arbitrary
|
||||
// content, aren't just downscaled
|
||||
bool should_force_safe_hashing = false; // for XFB
|
||||
bool is_xfb_copy = false;
|
||||
float y_scale = 1.0f;
|
||||
float gamma = 1.0f;
|
||||
u64 id;
|
||||
|
||||
bool reference_changed = false; // used by xfb to determine when a reference xfb changed
|
||||
|
||||
unsigned int native_width,
|
||||
native_height; // Texture dimensions from the GameCube's point of view
|
||||
|
@ -105,11 +152,13 @@ public:
|
|||
|
||||
~TCacheEntry();
|
||||
|
||||
void SetGeneralParameters(u32 _addr, u32 _size, TextureAndTLUTFormat _format)
|
||||
void SetGeneralParameters(u32 _addr, u32 _size, TextureAndTLUTFormat _format,
|
||||
bool force_safe_hashing)
|
||||
{
|
||||
addr = _addr;
|
||||
size_in_bytes = _size;
|
||||
format = _format;
|
||||
should_force_safe_hashing = force_safe_hashing;
|
||||
}
|
||||
|
||||
void SetDimensions(unsigned int _native_width, unsigned int _native_height,
|
||||
|
@ -135,16 +184,20 @@ public:
|
|||
other_entry->references.emplace(this);
|
||||
}
|
||||
|
||||
void SetXfbCopy(u32 stride);
|
||||
void SetEfbCopy(u32 stride);
|
||||
void SetNotCopy();
|
||||
|
||||
bool OverlapsMemoryRange(u32 range_address, u32 range_size) const;
|
||||
|
||||
bool IsEfbCopy() const { return is_efb_copy; }
|
||||
bool IsCopy() const { return is_xfb_copy || is_efb_copy; }
|
||||
u32 NumBlocksY() const;
|
||||
u32 BytesPerRow() const;
|
||||
|
||||
u64 CalculateHash() const;
|
||||
|
||||
int HashSampleSize() const;
|
||||
u32 GetWidth() const { return texture->GetConfig().width; }
|
||||
u32 GetHeight() const { return texture->GetConfig().height; }
|
||||
u32 GetNumLevels() const { return texture->GetConfig().levels; }
|
||||
|
@ -172,10 +225,31 @@ public:
|
|||
TCacheEntry* Load(const u32 stage);
|
||||
static void InvalidateAllBindPoints() { valid_bind_points.reset(); }
|
||||
static bool IsValidBindPoint(u32 i) { return valid_bind_points.test(i); }
|
||||
void BindTextures();
|
||||
TCacheEntry* GetTexture(u32 address, u32 width, u32 height, const TextureFormat texformat,
|
||||
const int textureCacheSafetyColorSampleSize, u32 tlutaddr = 0,
|
||||
TLUTFormat tlutfmt = TLUTFormat::IA8, bool use_mipmaps = false,
|
||||
u32 tex_levels = 1, bool from_tmem = false, u32 tmem_address_even = 0,
|
||||
u32 tmem_address_odd = 0);
|
||||
|
||||
TCacheEntry* GetXFBTexture(u32 address, u32 width, u32 height, TextureFormat texformat,
|
||||
int textureCacheSafetyColorSampleSize);
|
||||
std::optional<TextureLookupInformation>
|
||||
ComputeTextureInformation(u32 address, u32 width, u32 height, TextureFormat texformat,
|
||||
int textureCacheSafetyColorSampleSize, bool from_tmem,
|
||||
u32 tmem_address_even, u32 tmem_address_odd, u32 tlutaddr,
|
||||
TLUTFormat tlutfmt, u32 levels);
|
||||
TCacheEntry* GetXFBFromCache(const TextureLookupInformation& tex_info);
|
||||
bool LoadTextureFromOverlappingTextures(TCacheEntry* entry_to_update,
|
||||
const TextureLookupInformation& tex_info);
|
||||
TCacheEntry* CreateNormalTexture(const TextureLookupInformation& tex_info);
|
||||
void LoadTextureFromMemory(TCacheEntry* entry_to_update,
|
||||
const TextureLookupInformation& tex_info);
|
||||
void LoadTextureLevelZeroFromMemory(TCacheEntry* entry_to_update,
|
||||
const TextureLookupInformation& tex_info, bool decode_on_gpu);
|
||||
virtual void BindTextures();
|
||||
void CopyRenderTargetToTexture(u32 dstAddr, EFBCopyFormat dstFormat, u32 dstStride,
|
||||
bool is_depth_copy, const EFBRectangle& srcRect, bool isIntensity,
|
||||
bool scaleByHalf);
|
||||
bool scaleByHalf, float y_scale, float gamma);
|
||||
|
||||
virtual void ConvertTexture(TCacheEntry* entry, TCacheEntry* unconverted, const void* palette,
|
||||
TLUTFormat format) = 0;
|
||||
|
@ -197,6 +271,10 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
void ScaleTextureCacheEntryTo(TCacheEntry* entry, u32 new_width, u32 new_height);
|
||||
|
||||
virtual std::unique_ptr<AbstractTexture> CreateTexture(const TextureConfig& config) = 0;
|
||||
|
||||
protected:
|
||||
TextureCacheBase();
|
||||
|
||||
|
@ -222,7 +300,6 @@ private:
|
|||
|
||||
TCacheEntry* ApplyPaletteToEntry(TCacheEntry* entry, u8* palette, TLUTFormat tlutfmt);
|
||||
|
||||
void ScaleTextureCacheEntryTo(TCacheEntry* entry, u32 new_width, u32 new_height);
|
||||
TCacheEntry* DoPartialTextureUpdates(TCacheEntry* entry_to_update, u8* palette,
|
||||
TLUTFormat tlutfmt);
|
||||
|
||||
|
@ -239,8 +316,6 @@ private:
|
|||
std::pair<TexAddrCache::iterator, TexAddrCache::iterator>
|
||||
FindOverlappingTextures(u32 addr, u32 size_in_bytes);
|
||||
|
||||
virtual std::unique_ptr<AbstractTexture> CreateTexture(const TextureConfig& config) = 0;
|
||||
|
||||
virtual void CopyEFBToCacheEntry(TCacheEntry* entry, bool is_depth_copy,
|
||||
const EFBRectangle& src_rect, bool scale_by_half,
|
||||
unsigned int cbuf_id, const float* colmat) = 0;
|
||||
|
@ -248,11 +323,12 @@ private:
|
|||
// Removes and unlinks texture from texture cache and returns it to the pool
|
||||
TexAddrCache::iterator InvalidateTexture(TexAddrCache::iterator t_iter);
|
||||
|
||||
TCacheEntry* ReturnEntry(unsigned int stage, TCacheEntry* entry);
|
||||
void UninitializeXFBMemory(u8* dst, u32 stride, u32 bytes_per_row, u32 num_blocks_y);
|
||||
|
||||
TexAddrCache textures_by_address;
|
||||
TexHashCache textures_by_hash;
|
||||
TexPool texture_pool;
|
||||
u64 last_entry_id = 0;
|
||||
|
||||
// Backup configuration values
|
||||
struct BackupConfig
|
||||
|
|
|
@ -49,6 +49,8 @@ u16 GetEncodedSampleCount(EFBCopyFormat format)
|
|||
case EFBCopyFormat::RG8:
|
||||
case EFBCopyFormat::GB8:
|
||||
return 2;
|
||||
case EFBCopyFormat::XFB:
|
||||
return 2;
|
||||
default:
|
||||
PanicAlert("Invalid EFB Copy Format (0x%X)! (GetEncodedSampleCount)", static_cast<int>(format));
|
||||
return 1;
|
||||
|
@ -62,9 +64,13 @@ static void WriteSwizzler(char*& p, EFBCopyFormat format, APIType ApiType)
|
|||
// left, top, of source rectangle within source texture
|
||||
// width of the destination rectangle, scale_factor (1 or 2)
|
||||
if (ApiType == APIType::Vulkan)
|
||||
WRITE(p, "layout(std140, push_constant) uniform PCBlock { int4 position; } PC;\n");
|
||||
WRITE(p,
|
||||
"layout(std140, push_constant) uniform PCBlock { int4 position; float y_scale; } PC;\n");
|
||||
else
|
||||
{
|
||||
WRITE(p, "uniform int4 position;\n");
|
||||
WRITE(p, "uniform float y_scale;\n");
|
||||
}
|
||||
|
||||
// Alpha channel in the copy is set to 1 the EFB format does not have an alpha channel.
|
||||
WRITE(p, "float4 RGBA8ToRGB8(float4 src)\n");
|
||||
|
@ -109,7 +115,8 @@ static void WriteSwizzler(char*& p, EFBCopyFormat format, APIType ApiType)
|
|||
WRITE(p, "{\n"
|
||||
" int2 sampleUv;\n"
|
||||
" int2 uv1 = int2(gl_FragCoord.xy);\n"
|
||||
" int4 position = PC.position;\n");
|
||||
" int4 position = PC.position;\n"
|
||||
" float y_scale = PC.y_scale;\n");
|
||||
}
|
||||
else // D3D
|
||||
{
|
||||
|
@ -148,6 +155,7 @@ static void WriteSwizzler(char*& p, EFBCopyFormat format, APIType ApiType)
|
|||
// pixel)
|
||||
WRITE(p, " uv0 += float2(position.xy);\n"); // move to copied rect
|
||||
WRITE(p, " uv0 /= float2(%d, %d);\n", EFB_WIDTH, EFB_HEIGHT); // normalize to [0:1]
|
||||
WRITE(p, " uv0 /= float2(1, y_scale);\n"); // apply the y scaling
|
||||
if (ApiType == APIType::OpenGL) // ogl has to flip up and down
|
||||
{
|
||||
WRITE(p, " uv0.y = 1.0-uv0.y;\n");
|
||||
|
@ -656,6 +664,28 @@ static void WriteZ24Encoder(char*& p, APIType ApiType, const EFBCopyParams& para
|
|||
WriteEncoderEnd(p);
|
||||
}
|
||||
|
||||
static void WriteXFBEncoder(char*& p, APIType ApiType, const EFBCopyParams& params)
|
||||
{
|
||||
WriteSwizzler(p, EFBCopyFormat::XFB, ApiType);
|
||||
|
||||
WRITE(p, " float3 y_const = float3(0.257, 0.504, 0.098);\n");
|
||||
WRITE(p, " float3 u_const = float3(-0.148, -0.291, 0.439);\n");
|
||||
WRITE(p, " float3 v_const = float3(0.439, -0.368, -0.071);\n");
|
||||
WRITE(p, " float3 color0;\n");
|
||||
WRITE(p, " float3 color1;\n");
|
||||
|
||||
WriteSampleColor(p, "rgb", "color0", 0, ApiType, params);
|
||||
WriteSampleColor(p, "rgb", "color1", 1, ApiType, params);
|
||||
WRITE(p, " float3 average = (color0 + color1) * 0.5;\n");
|
||||
|
||||
WRITE(p, " ocol0.b = dot(color0, y_const) + 0.0625;\n");
|
||||
WRITE(p, " ocol0.g = dot(average, u_const) + 0.5;\n");
|
||||
WRITE(p, " ocol0.r = dot(color1, y_const) + 0.0625;\n");
|
||||
WRITE(p, " ocol0.a = dot(average, v_const) + 0.5;\n");
|
||||
|
||||
WriteEncoderEnd(p);
|
||||
}
|
||||
|
||||
const char* GenerateEncodingShader(const EFBCopyParams& params, APIType api_type)
|
||||
{
|
||||
text[sizeof(text) - 1] = 0x7C; // canary
|
||||
|
@ -728,6 +758,9 @@ const char* GenerateEncodingShader(const EFBCopyParams& params, APIType api_type
|
|||
else
|
||||
WriteCC8Encoder(p, "gb", api_type, params);
|
||||
break;
|
||||
case EFBCopyFormat::XFB:
|
||||
WriteXFBEncoder(p, api_type, params);
|
||||
break;
|
||||
default:
|
||||
PanicAlert("Invalid EFB Copy Format (0x%X)! (GenerateEncodingShader)",
|
||||
static_cast<int>(params.copy_format));
|
||||
|
@ -1230,12 +1263,41 @@ static const std::map<TextureFormat, DecodingShaderInfo> s_decoding_shader_info{
|
|||
vec4 norm_color = GetPaletteColorNormalized(index);
|
||||
imageStore(output_image, ivec3(ivec2(coords), 0), norm_color);
|
||||
}
|
||||
)"}},
|
||||
|
||||
// We do the inverse BT.601 conversion for YCbCr to RGB
|
||||
// http://www.equasys.de/colorconversion.html#YCbCr-RGBColorFormatConversion
|
||||
{TextureFormat::XFB,
|
||||
{BUFFER_FORMAT_RGBA8_UINT, 0, 8, 8, false,
|
||||
R"(
|
||||
layout(local_size_x = 8, local_size_y = 8) in;
|
||||
|
||||
void main()
|
||||
{
|
||||
uvec2 uv = gl_GlobalInvocationID.xy;
|
||||
int buffer_pos = int(u_src_offset + (uv.y * u_src_row_stride) + (uv.x / 2));
|
||||
vec4 yuyv = texelFetch(s_input_buffer, buffer_pos);
|
||||
|
||||
float y = mix(yuyv.r, yuyv.b, (uv.x & 1u) == 1u);
|
||||
|
||||
float yComp = 1.164 * (y - 16);
|
||||
float uComp = yuyv.g - 128;
|
||||
float vComp = yuyv.a - 128;
|
||||
|
||||
vec4 rgb = vec4(yComp + (1.596 * vComp),
|
||||
yComp - (0.813 * vComp) - (0.391 * uComp),
|
||||
yComp + (2.018 * uComp),
|
||||
255.0);
|
||||
vec4 rgba_norm = rgb / 255.0;
|
||||
imageStore(output_image, ivec3(ivec2(uv), 0), rgba_norm);
|
||||
}
|
||||
)"}}};
|
||||
|
||||
static const std::array<u32, BUFFER_FORMAT_COUNT> s_buffer_bytes_per_texel = {{
|
||||
1, // BUFFER_FORMAT_R8_UINT
|
||||
2, // BUFFER_FORMAT_R16_UINT
|
||||
8, // BUFFER_FORMAT_R32G32_UINT
|
||||
4, // BUFFER_FORMAT_RGBA8_UINT
|
||||
}};
|
||||
|
||||
const DecodingShaderInfo* GetDecodingShaderInfo(TextureFormat format)
|
||||
|
|
|
@ -27,6 +27,7 @@ enum BufferFormat
|
|||
BUFFER_FORMAT_R8_UINT,
|
||||
BUFFER_FORMAT_R16_UINT,
|
||||
BUFFER_FORMAT_R32G32_UINT,
|
||||
BUFFER_FORMAT_RGBA8_UINT,
|
||||
BUFFER_FORMAT_COUNT
|
||||
};
|
||||
|
||||
|
|
|
@ -28,6 +28,11 @@ enum class TextureFormat
|
|||
C8 = 0x9,
|
||||
C14X2 = 0xA,
|
||||
CMPR = 0xE,
|
||||
|
||||
// Special texture format used to represent YUVY xfb copies.
|
||||
// They aren't really textures, but they share so much hardware and usecases that it makes sense
|
||||
// to emulate them as part of texture cache.
|
||||
XFB = 0xF,
|
||||
};
|
||||
|
||||
static inline bool IsColorIndexed(TextureFormat format)
|
||||
|
@ -73,6 +78,11 @@ enum class EFBCopyFormat
|
|||
B8 = 0xA, // B8, Z8L
|
||||
RG8 = 0xB, // RG8, Z16R (Note: G and R are reversed)
|
||||
GB8 = 0xC, // GB8, Z16L
|
||||
|
||||
// Special texture format used to represent YUVY xfb copies.
|
||||
// They aren't really textures, but they share so much hardware and usecases that it makes sense
|
||||
// to emulate them as part of texture cache.
|
||||
XFB = 0xF,
|
||||
};
|
||||
|
||||
enum class TLUTFormat
|
||||
|
|
|
@ -4,8 +4,10 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/MathUtil.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Common/Swap.h"
|
||||
|
||||
|
@ -46,6 +48,9 @@ int TexDecoder_GetTexelSizeInNibbles(TextureFormat format)
|
|||
// Compressed format
|
||||
case TextureFormat::CMPR:
|
||||
return 1;
|
||||
// Special formats
|
||||
case TextureFormat::XFB:
|
||||
return 4;
|
||||
default:
|
||||
PanicAlert("Invalid Texture Format (0x%X)! (GetTexelSizeInNibbles)", static_cast<int>(format));
|
||||
return 1;
|
||||
|
@ -82,6 +87,9 @@ int TexDecoder_GetBlockWidthInTexels(TextureFormat format)
|
|||
// Compressed format
|
||||
case TextureFormat::CMPR:
|
||||
return 8;
|
||||
// Special formats
|
||||
case TextureFormat::XFB:
|
||||
return 16;
|
||||
default:
|
||||
PanicAlert("Invalid Texture Format (0x%X)! (GetBlockWidthInTexels)", static_cast<int>(format));
|
||||
return 8;
|
||||
|
@ -113,6 +121,9 @@ int TexDecoder_GetBlockHeightInTexels(TextureFormat format)
|
|||
// Compressed format
|
||||
case TextureFormat::CMPR:
|
||||
return 8;
|
||||
// Special formats
|
||||
case TextureFormat::XFB:
|
||||
return 1;
|
||||
default:
|
||||
PanicAlert("Invalid Texture Format (0x%X)! (GetBlockHeightInTexels)", static_cast<int>(format));
|
||||
return 4;
|
||||
|
@ -144,6 +155,9 @@ int TexDecoder_GetEFBCopyBlockWidthInTexels(EFBCopyFormat format)
|
|||
// 32-bit formats
|
||||
case EFBCopyFormat::RGBA8:
|
||||
return 4;
|
||||
// Special formats
|
||||
case EFBCopyFormat::XFB:
|
||||
return 16;
|
||||
default:
|
||||
PanicAlert("Invalid EFB Copy Format (0x%X)! (GetEFBCopyBlockWidthInTexels)",
|
||||
static_cast<int>(format));
|
||||
|
@ -176,6 +190,9 @@ int TexDecoder_GetEFBCopyBlockHeightInTexels(EFBCopyFormat format)
|
|||
// 32-bit formats
|
||||
case EFBCopyFormat::RGBA8:
|
||||
return 4;
|
||||
// Special formats
|
||||
case EFBCopyFormat::XFB:
|
||||
return 1;
|
||||
default:
|
||||
PanicAlert("Invalid EFB Copy Format (0x%X)! (GetEFBCopyBlockHeightInTexels)",
|
||||
static_cast<int>(format));
|
||||
|
@ -226,6 +243,8 @@ TextureFormat TexDecoder_GetEFBCopyBaseFormat(EFBCopyFormat format)
|
|||
return TextureFormat::RGB5A3;
|
||||
case EFBCopyFormat::RGBA8:
|
||||
return TextureFormat::RGBA8;
|
||||
case EFBCopyFormat::XFB:
|
||||
return TextureFormat::XFB;
|
||||
default:
|
||||
PanicAlert("Invalid EFB Copy Format (0x%X)! (GetEFBCopyBaseFormat)", static_cast<int>(format));
|
||||
return static_cast<TextureFormat>(format);
|
||||
|
@ -247,7 +266,7 @@ static const char* texfmt[] = {
|
|||
"0x1C", "0x1D", "0x1E", "0x1F",
|
||||
// pixel + copy
|
||||
"CR4", "0x21", "CRA4", "CRA8", "0x24", "0x25", "CYUVA8", "CA8", "CR8", "CG8", "CB8", "CRG8",
|
||||
"CGB8", "0x2D", "0x2E", "0x2F",
|
||||
"CGB8", "0x2D", "0x2E", "XFB",
|
||||
// Z + copy
|
||||
"CZ4", "0x31", "0x32", "0x33", "0x34", "0x35", "0x36", "0x37", "0x38", "CZ8M", "CZ8L", "0x3B",
|
||||
"CZ16L", "0x3D", "0x3E", "0x3F",
|
||||
|
@ -619,6 +638,23 @@ void TexDecoder_DecodeTexel(u8* dst, const u8* src, int s, int t, int imageWidth
|
|||
*((u32*)dst) = color;
|
||||
}
|
||||
break;
|
||||
case TextureFormat::XFB:
|
||||
{
|
||||
size_t offset = (t * imageWidth + (s & (~1))) * 2;
|
||||
|
||||
// We do this one color sample (aka 2 RGB pixles) at a time
|
||||
int Y = int((s & 1) == 0 ? src[offset] : src[offset + 2]) - 16;
|
||||
int U = int(src[offset + 1]) - 128;
|
||||
int V = int(src[offset + 3]) - 128;
|
||||
|
||||
// We do the inverse BT.601 conversion for YCbCr to RGB
|
||||
// http://www.equasys.de/colorconversion.html#YCbCr-RGBColorFormatConversion
|
||||
u8 R = MathUtil::Clamp(int(1.164f * Y + 1.596f * V), 0, 255);
|
||||
u8 G = MathUtil::Clamp(int(1.164f * Y - 0.392f * U - 0.813f * V), 0, 255);
|
||||
u8 B = MathUtil::Clamp(int(1.164f * Y + 2.017f * U), 0, 255);
|
||||
dst[t * imageWidth + s] = 0xff000000 | B << 16 | G << 8 | R;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "Common/CPUDetect.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Intrinsics.h"
|
||||
#include "Common/MathUtil.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Common/Swap.h"
|
||||
|
||||
|
@ -1486,6 +1487,39 @@ void _TexDecoder_DecodeImpl(u32* dst, const u8* src, int width, int height, Text
|
|||
TexDecoder_DecodeImpl_CMPR(dst, src, width, height, texformat, tlut, tlutfmt, Wsteps4, Wsteps8);
|
||||
break;
|
||||
|
||||
case TextureFormat::XFB:
|
||||
{
|
||||
for (int y = 0; y < height; y += 1)
|
||||
{
|
||||
for (int x = 0; x < width; x += 2)
|
||||
{
|
||||
size_t offset = static_cast<size_t>((y * width + x) * 2);
|
||||
|
||||
// We do this one color sample (aka 2 RGB pixles) at a time
|
||||
int Y1 = int(src[offset]) - 16;
|
||||
int U = int(src[offset + 1]) - 128;
|
||||
int Y2 = int(src[offset + 2]) - 16;
|
||||
int V = int(src[offset + 3]) - 128;
|
||||
|
||||
// We do the inverse BT.601 conversion for YCbCr to RGB
|
||||
// http://www.equasys.de/colorconversion.html#YCbCr-RGBColorFormatConversion
|
||||
u8 R1 = static_cast<u8>(MathUtil::Clamp(int(1.164f * Y1 + 1.596f * V), 0, 255));
|
||||
u8 G1 =
|
||||
static_cast<u8>(MathUtil::Clamp(int(1.164f * Y1 - 0.392f * U - 0.813f * V), 0, 255));
|
||||
u8 B1 = static_cast<u8>(MathUtil::Clamp(int(1.164f * Y1 + 2.017f * U), 0, 255));
|
||||
|
||||
u8 R2 = static_cast<u8>(MathUtil::Clamp(int(1.164f * Y2 + 1.596f * V), 0, 255));
|
||||
u8 G2 =
|
||||
static_cast<u8>(MathUtil::Clamp(int(1.164f * Y2 - 0.392f * U - 0.813f * V), 0, 255));
|
||||
u8 B2 = static_cast<u8>(MathUtil::Clamp(int(1.164f * Y2 + 2.017f * U), 0, 255));
|
||||
|
||||
dst[y * width + x] = 0xff000000 | B1 << 16 | G1 << 8 | R1;
|
||||
dst[y * width + x + 1] = 0xff000000 | B2 << 16 | G2 << 8 | R2;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
PanicAlert("Invalid Texture Format (0x%X)! (_TexDecoder_DecodeImpl)",
|
||||
static_cast<int>(texformat));
|
||||
|
|
|
@ -47,7 +47,9 @@ public:
|
|||
|
||||
virtual void Video_Prepare() = 0;
|
||||
void Video_ExitLoop();
|
||||
virtual void Video_Cleanup() = 0; // called from gl/d3d thread
|
||||
|
||||
void Video_CleanupShared(); // called from gl/d3d thread
|
||||
virtual void Video_Cleanup() = 0;
|
||||
|
||||
void Video_BeginField(u32, u32, u32, u32, u64);
|
||||
|
||||
|
|
|
@ -55,6 +55,8 @@ struct TargetRectangle : public MathUtil::Rectangle<int>
|
|||
return (RECT*)this;
|
||||
}
|
||||
#endif
|
||||
TargetRectangle(const MathUtil::Rectangle<int>& other) : MathUtil::Rectangle<int>(other) {}
|
||||
TargetRectangle() = default;
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
|
|
|
@ -180,4 +180,4 @@
|
|||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
|
@ -173,6 +173,9 @@
|
|||
<ClCompile Include="AbstractTexture.cpp">
|
||||
<Filter>Base</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="AbstractRawTexture.cpp">
|
||||
<Filter>Base</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ShaderGenCommon.cpp">
|
||||
<Filter>Shader Generators</Filter>
|
||||
</ClCompile>
|
||||
|
@ -344,6 +347,9 @@
|
|||
<ClInclude Include="AbstractTexture.h">
|
||||
<Filter>Base</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="AbstractRawTexture.h">
|
||||
<Filter>Base</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="AsyncShaderCompiler.h">
|
||||
<Filter>Util</Filter>
|
||||
</ClInclude>
|
||||
|
@ -360,4 +366,4 @@
|
|||
<ItemGroup>
|
||||
<Text Include="CMakeLists.txt" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue