GS: route texture upload and readback through the flush point

The GSDevice entry points are not the whole story. A CPU upload into a texture
goes through GSTexture::Update and a readback goes through
GSDownloadTexture::CopyFromTexture, neither of which is a GSDevice method, so
both would let a deferred draw be reordered past work that must not move:

  - Update into a target a queued draw writes, or into a texture a queued draw
    samples. In the original order the draw sees the old contents; deferred
    past the upload, it would see the new ones.
  - CopyFromTexture is the actual readback - CreateDownloadTexture only
    allocates, and the texture cache reuses those - so guarding creation would
    not have covered it.

Same treatment as the device entry points: the virtual becomes a protected
Do* form and the public name becomes a non-virtual wrapper that flushes first,
which the pure base virtual makes compiler-enforced across all six backends.

Still unguarded and left to the debug tripwire: GSTexture::Map and
GenerateMipmap. Map cannot get the same mechanical rename because
GSDownloadTexture declares an unrelated Map in the same headers, and neither
is on a path that writes a render target today.
This commit is contained in:
Brian Degenhardt
2026-07-25 17:28:20 -07:00
parent 518418b3ac
commit 1cbbe8b999
14 changed files with 60 additions and 35 deletions
+13
View File
@@ -16,6 +16,12 @@ GSTexture::GSTexture() = default;
GSTexture::~GSTexture() = default;
bool GSTexture::Update(const GSVector4i& r, const void* data, int pitch, int layer)
{
g_gs_device->FlushDeferredDraws();
return DoUpdate(r, data, pitch, layer);
}
bool GSTexture::ValidateUsageAndFormat(Usage usage, Format format)
{
if (IsDepthStencil(usage) && (usage & (Usage::ShaderWrite | Usage::RenderTarget)))
@@ -229,6 +235,13 @@ GSDownloadTexture::GSDownloadTexture(u32 width, u32 height, GSTexture::Format fo
GSDownloadTexture::~GSDownloadTexture() = default;
void GSDownloadTexture::CopyFromTexture(
const GSVector4i& drc, GSTexture* stex, const GSVector4i& src, u32 src_level, bool use_transfer_pitch)
{
g_gs_device->FlushDeferredDraws();
DoCopyFromTexture(drc, stex, src, src_level, use_transfer_pitch);
}
u32 GSDownloadTexture::GetBufferSize(u32 width, u32 height, GSTexture::Format format, u32 pitch_align /* = 1 */)
{
const u32 block_size = GSTexture::GetCompressedBlockSize(format);
+15 -3
View File
@@ -97,6 +97,9 @@ protected:
#ifdef PCSX2_DEVBUILD
std::string m_debug_name;
#endif
virtual bool DoUpdate(const GSVector4i& r, const void* data, int pitch, int layer = 0) = 0;
public:
GSTexture();
virtual ~GSTexture();
@@ -104,7 +107,10 @@ public:
// Returns the native handle of a texture.
virtual void* GetNativeHandle() const = 0;
virtual bool Update(const GSVector4i& r, const void* data, int pitch, int layer = 0) = 0;
/// Uploads CPU data into the texture. Flushes any deferred draws first: a draw held
/// back by GSPassScheduler must not be reordered past an upload into a texture it
/// writes, nor past one into a texture it samples.
bool Update(const GSVector4i& r, const void* data, int pitch, int layer = 0);
virtual bool Map(GSMap& m, const GSVector4i* r = nullptr, int layer = 0) = 0;
virtual void Unmap() = 0;
virtual void GenerateMipmap() = 0;
@@ -262,6 +268,10 @@ public:
class GSDownloadTexture
{
protected:
virtual void DoCopyFromTexture(
const GSVector4i& drc, GSTexture* stex, const GSVector4i& src, u32 src_level, bool use_transfer_pitch) = 0;
public:
GSDownloadTexture(u32 width, u32 height, GSTexture::Format format);
virtual ~GSDownloadTexture();
@@ -285,8 +295,10 @@ public:
/// Does not complete immediately, you should flush before accessing the buffer.
/// use_transfer_pitch should be true if there's only a single texture being copied to this buffer before
/// it will be used. This allows the image to be packed tighter together, and buffer reuse.
virtual void CopyFromTexture(
const GSVector4i& drc, GSTexture* stex, const GSVector4i& src, u32 src_level, bool use_transfer_pitch = true) = 0;
/// Flushes any deferred draws first, so a readback always sees every draw that had been
/// submitted when it was issued.
void CopyFromTexture(
const GSVector4i& drc, GSTexture* stex, const GSVector4i& src, u32 src_level, bool use_transfer_pitch = true);
/// Maps the texture into the CPU address space, enabling it to read the contents.
/// The Map call may not perform synchronization. If the contents of the staging texture
+2 -2
View File
@@ -55,7 +55,7 @@ void* GSTexture11::GetNativeHandle() const
return static_cast<ID3D11ShaderResourceView*>(*const_cast<GSTexture11*>(this));
}
bool GSTexture11::Update(const GSVector4i& r, const void* data, int pitch, int layer)
bool GSTexture11::DoUpdate(const GSVector4i& r, const void* data, int pitch, int layer)
{
if (layer >= m_mipmap_levels)
return false;
@@ -232,7 +232,7 @@ std::unique_ptr<GSDownloadTexture11> GSDownloadTexture11::Create(u32 width, u32
return std::unique_ptr<GSDownloadTexture11>(new GSDownloadTexture11(std::move(tex), width, height, format));
}
void GSDownloadTexture11::CopyFromTexture(
void GSDownloadTexture11::DoCopyFromTexture(
const GSVector4i& drc, GSTexture* stex, const GSVector4i& src, u32 src_level, bool use_transfer_pitch)
{
pxAssert(stex->GetFormat() == m_format);
+2 -2
View File
@@ -27,7 +27,7 @@ public:
void* GetNativeHandle() const override;
bool Update(const GSVector4i& r, const void* data, int pitch, int layer = 0) override;
bool DoUpdate(const GSVector4i& r, const void* data, int pitch, int layer = 0) override;
bool Map(GSMap& m, const GSVector4i* r = NULL, int layer = 0) override;
void Unmap() override;
void GenerateMipmap() override;
@@ -52,7 +52,7 @@ public:
static std::unique_ptr<GSDownloadTexture11> Create(u32 width, u32 height, GSTexture::Format format);
void CopyFromTexture(
void DoCopyFromTexture(
const GSVector4i& drc, GSTexture* stex, const GSVector4i& src, u32 src_level, bool use_transfer_pitch) override;
bool Map(const GSVector4i& rc) override;
+5 -5
View File
@@ -578,7 +578,7 @@ void GSTexture12::CopyTextureDataForUpload(void* dst, const void* src, u32 pitch
StringUtil::StrideMemCpy(dst, upload_pitch, src, pitch, std::min(upload_pitch, pitch), count);
}
bool GSTexture12::Update(const GSVector4i& r, const void* data, int pitch, int layer)
bool GSTexture12::DoUpdate(const GSVector4i& r, const void* data, int pitch, int layer)
{
if (layer >= m_mipmap_levels)
return false;
@@ -631,7 +631,7 @@ bool GSTexture12::Update(const GSVector4i& r, const void* data, int pitch, int l
}
const D3D12CommandList& cmdlist = GetCommandBufferForUpdate();
GL_PUSH("GSTexture12::Update({%d,%d} %dx%d Lvl:%u", r.x, r.y, r.width(), r.height(), layer);
GL_PUSH("GSTexture12::DoUpdate({%d,%d} %dx%d Lvl:%u", r.x, r.y, r.width(), r.height(), layer);
// first time the texture is used? don't leave it undefined
if (m_resource_state == GSTexture12::ResourceState::Undefined)
@@ -676,7 +676,7 @@ bool GSTexture12::Map(GSMap& m, const GSVector4i* r, int layer)
m_map_level = layer;
m.pitch = Common::AlignUpPow2(CalcUploadPitch(m_map_area.width()), D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
// see note in Update() for the reason why.
// see note in DoUpdate() for the reason why.
const u32 required_size = CalcUploadSize(m_map_area.height(), m.pitch);
D3D12StreamBuffer& buffer = GSDevice12::GetInstance()->GetTextureStreamBuffer();
if (required_size >= (buffer.GetSize() / 2))
@@ -709,7 +709,7 @@ void GSTexture12::Unmap()
buffer.CommitMemory(required_size);
const D3D12CommandList& cmdlist = GetCommandBufferForUpdate();
GL_PUSH("GSTexture12::Update({%d,%d} %dx%d Lvl:%u", m_map_area.x, m_map_area.y, m_map_area.width(),
GL_PUSH("GSTexture12::DoUpdate({%d,%d} %dx%d Lvl:%u", m_map_area.x, m_map_area.y, m_map_area.width(),
m_map_area.height(), m_map_level);
// first time the texture is used? don't leave it undefined
@@ -1209,7 +1209,7 @@ std::unique_ptr<GSDownloadTexture12> GSDownloadTexture12::Create(u32 width, u32
return tex;
}
void GSDownloadTexture12::CopyFromTexture(
void GSDownloadTexture12::DoCopyFromTexture(
const GSVector4i& drc, GSTexture* stex, const GSVector4i& src, u32 src_level, bool use_transfer_pitch)
{
GSTexture12* const tex12 = static_cast<GSTexture12*>(stex);
+2 -2
View File
@@ -60,7 +60,7 @@ public:
void* GetNativeHandle() const override;
bool Update(const GSVector4i& r, const void* data, int pitch, int layer = 0) override;
bool DoUpdate(const GSVector4i& r, const void* data, int pitch, int layer = 0) override;
bool Map(GSMap& m, const GSVector4i* r = NULL, int layer = 0) override;
void Unmap() override;
void GenerateMipmap() override;
@@ -140,7 +140,7 @@ public:
static std::unique_ptr<GSDownloadTexture12> Create(u32 width, u32 height, GSTexture::Format format);
void CopyFromTexture(
void DoCopyFromTexture(
const GSVector4i& drc, GSTexture* stex, const GSVector4i& src, u32 src_level, bool use_transfer_pitch) override;
bool Map(const GSVector4i& read_rc) override;
+2 -2
View File
@@ -37,7 +37,7 @@ public:
void FlushClears();
void* GetNativeHandle() const override;
bool Update(const GSVector4i& r, const void* data, int pitch, int layer = 0) override;
bool DoUpdate(const GSVector4i& r, const void* data, int pitch, int layer = 0) override;
bool Map(GSMap& m, const GSVector4i* r = NULL, int layer = 0) override;
void* MapWithPitch(const GSVector4i& r, int pitch, int layer);
void Unmap() override;
@@ -57,7 +57,7 @@ public:
static std::unique_ptr<GSDownloadTextureMTL> Create(GSDeviceMTL* dev, u32 width, u32 height, GSTexture::Format format);
void CopyFromTexture(const GSVector4i& drc, GSTexture* stex, const GSVector4i& src, u32 src_level, bool use_transfer_pitch) override;
void DoCopyFromTexture(const GSVector4i& drc, GSTexture* stex, const GSVector4i& src, u32 src_level, bool use_transfer_pitch) override;
bool Map(const GSVector4i& read_rc) override;
void Unmap() override;
+3 -3
View File
@@ -43,7 +43,7 @@ void* GSTextureMTL::GetNativeHandle() const
return (__bridge void*)m_texture;
}
bool GSTextureMTL::Update(const GSVector4i& r, const void* data, int pitch, int layer)
bool GSTextureMTL::DoUpdate(const GSVector4i& r, const void* data, int pitch, int layer)
{
if (void* buffer = MapWithPitch(r, pitch, layer))
{
@@ -176,7 +176,7 @@ std::unique_ptr<GSDownloadTextureMTL> GSDownloadTextureMTL::Create(GSDeviceMTL*
return std::unique_ptr<GSDownloadTextureMTL>(new GSDownloadTextureMTL(dev, buffer, width, height, format));
}}
void GSDownloadTextureMTL::CopyFromTexture(
void GSDownloadTextureMTL::DoCopyFromTexture(
const GSVector4i& drc, GSTexture* stex, const GSVector4i& src, u32 src_level, bool use_transfer_pitch)
{ @autoreleasepool {
GSTextureMTL* const mtlTex = static_cast<GSTextureMTL*>(stex);
@@ -199,7 +199,7 @@ void GSDownloadTextureMTL::CopyFromTexture(
m_copy_cmdbuffer = MRCRetain(m_dev->GetRenderCmdBuf());
[m_copy_cmdbuffer pushDebugGroup:@"GSDownloadTextureMTL::CopyFromTexture"];
[m_copy_cmdbuffer pushDebugGroup:@"GSDownloadTextureMTL::DoCopyFromTexture"];
id<MTLBlitCommandEncoder> encoder = [m_copy_cmdbuffer blitCommandEncoder];
[encoder copyFromTexture:mtlTex->GetTexture()
sourceSlice:0
+2 -2
View File
@@ -20,7 +20,7 @@ void* GSTextureNone::GetNativeHandle() const
return nullptr;
}
bool GSTextureNone::Update(const GSVector4i& r, const void* data, int pitch, int layer)
bool GSTextureNone::DoUpdate(const GSVector4i& r, const void* data, int pitch, int layer)
{
return true;
}
@@ -61,7 +61,7 @@ GSDownloadTextureNone::GSDownloadTextureNone(u32 width, u32 height, GSTexture::F
m_buffer.resize(GetBufferSize(width, height, format));
}
void GSDownloadTextureNone::CopyFromTexture(
void GSDownloadTextureNone::DoCopyFromTexture(
const GSVector4i& drc, GSTexture* stex, const GSVector4i& src, u32 src_level, bool use_transfer_pitch)
{
m_current_pitch = GetTransferPitch(use_transfer_pitch ? static_cast<u32>(drc.width()) : m_width, 1);
+2 -2
View File
@@ -20,7 +20,7 @@ public:
GSTextureNone(Usage usage, int width, int height, int levels, Format format);
void* GetNativeHandle() const override;
bool Update(const GSVector4i& r, const void* data, int pitch, int layer = 0) override;
bool DoUpdate(const GSVector4i& r, const void* data, int pitch, int layer = 0) override;
bool Map(GSMap& m, const GSVector4i* r = nullptr, int layer = 0) override;
void Unmap() override;
void GenerateMipmap() override;
@@ -38,7 +38,7 @@ class GSDownloadTextureNone final : public GSDownloadTexture
public:
GSDownloadTextureNone(u32 width, u32 height, GSTexture::Format format);
void CopyFromTexture(const GSVector4i& drc, GSTexture* stex, const GSVector4i& src, u32 src_level,
void DoCopyFromTexture(const GSVector4i& drc, GSTexture* stex, const GSVector4i& src, u32 src_level,
bool use_transfer_pitch = true) override;
bool Map(const GSVector4i& read_rc) override;
void Unmap() override;
+2 -2
View File
@@ -179,7 +179,7 @@ void* GSTextureOGL::GetNativeHandle() const
return reinterpret_cast<void*>(static_cast<uintptr_t>(m_texture_id));
}
bool GSTextureOGL::Update(const GSVector4i& r, const void* data, int pitch, int layer)
bool GSTextureOGL::DoUpdate(const GSVector4i& r, const void* data, int pitch, int layer)
{
pxAssert(!IsDepthStencil());
@@ -416,7 +416,7 @@ std::unique_ptr<GSDownloadTextureOGL> GSDownloadTextureOGL::Create(u32 width, u3
return ret;
}
void GSDownloadTextureOGL::CopyFromTexture(
void GSDownloadTextureOGL::DoCopyFromTexture(
const GSVector4i& drc, GSTexture* stex, const GSVector4i& src, u32 src_level, bool use_transfer_pitch)
{
GSTextureOGL* const glTex = static_cast<GSTextureOGL*>(stex);
+2 -2
View File
@@ -38,7 +38,7 @@ public:
void* GetNativeHandle() const override;
bool Update(const GSVector4i& r, const void* data, int pitch, int layer = 0) override;
bool DoUpdate(const GSVector4i& r, const void* data, int pitch, int layer = 0) override;
bool Map(GSMap& m, const GSVector4i* r = NULL, int layer = 0) override;
void Unmap() override;
void GenerateMipmap() override;
@@ -67,7 +67,7 @@ public:
static std::unique_ptr<GSDownloadTextureOGL> Create(u32 width, u32 height, GSTexture::Format format);
void CopyFromTexture(const GSVector4i& drc, GSTexture* stex, const GSVector4i& src, u32 src_level, bool use_transfer_pitch) override;
void DoCopyFromTexture(const GSVector4i& drc, GSTexture* stex, const GSVector4i& src, u32 src_level, bool use_transfer_pitch) override;
bool Map(const GSVector4i& read_rc) override;
void Unmap() override;
+6 -6
View File
@@ -329,7 +329,7 @@ void GSTextureVK::UpdateFromBuffer(VkCommandBuffer cmdbuf, int level, u32 x, u32
TransitionSubresourcesToLayout(cmdbuf, level, 1, Layout::TransferDst, old_layout);
}
bool GSTextureVK::Update(const GSVector4i& r, const void* data, int pitch, int layer)
bool GSTextureVK::DoUpdate(const GSVector4i& r, const void* data, int pitch, int layer)
{
if (layer >= m_mipmap_levels)
return false;
@@ -383,7 +383,7 @@ bool GSTextureVK::Update(const GSVector4i& r, const void* data, int pitch, int l
}
const VkCommandBuffer cmdbuf = GetCommandBufferForUpdate();
GL_PUSH("GSTextureVK::Update({%d,%d} %dx%d Lvl:%u", r.x, r.y, r.width(), r.height(), layer);
GL_PUSH("GSTextureVK::DoUpdate({%d,%d} %dx%d Lvl:%u", r.x, r.y, r.width(), r.height(), layer);
// first time the texture is used? don't leave it undefined
if (m_layout == Layout::Undefined)
@@ -420,7 +420,7 @@ bool GSTextureVK::Map(GSMap& m, const GSVector4i* r, int layer)
m.pitch = Common::AlignUpPow2(
CalcUploadPitch(m_map_area.width()), GSDeviceVK::GetInstance()->GetBufferCopyRowPitchAlignment());
// see note in Update() for the reason why.
// see note in DoUpdate() for the reason why.
const u32 required_size = CalcUploadSize(m_map_area.height(), m.pitch);
VKStreamBuffer& buffer = GSDeviceVK::GetInstance()->GetTextureUploadBuffer();
if (required_size >= (buffer.GetCurrentSize() / 2))
@@ -464,7 +464,7 @@ void GSTextureVK::Unmap()
buffer.CommitMemory(required_size);
const VkCommandBuffer cmdbuf = GetCommandBufferForUpdate();
GL_PUSH("GSTextureVK::Update({%d,%d} %dx%d Lvl:%u", m_map_area.x, m_map_area.y, m_map_area.width(),
GL_PUSH("GSTextureVK::DoUpdate({%d,%d} %dx%d Lvl:%u", m_map_area.x, m_map_area.y, m_map_area.width(),
m_map_area.height(), m_map_level);
// first time the texture is used? don't leave it undefined
@@ -883,7 +883,7 @@ std::unique_ptr<GSDownloadTextureVK> GSDownloadTextureVK::Create(u32 width, u32
return tex;
}
void GSDownloadTextureVK::CopyFromTexture(
void GSDownloadTextureVK::DoCopyFromTexture(
const GSVector4i& drc, GSTexture* stex, const GSVector4i& src, u32 src_level, bool use_transfer_pitch)
{
GSTextureVK* const vkTex = static_cast<GSTextureVK*>(stex);
@@ -905,7 +905,7 @@ void GSDownloadTextureVK::CopyFromTexture(
vkTex->CommitClear();
const VkCommandBuffer cmdbuf = GSDeviceVK::GetInstance()->GetCurrentCommandBuffer();
GL_INS("GSDownloadTextureVK::CopyFromTexture: {%d,%d} %ux%u", src.left, src.top, src.width(), src.height());
GL_INS("GSDownloadTextureVK::DoCopyFromTexture: {%d,%d} %ux%u", src.left, src.top, src.width(), src.height());
GSTextureVK::Layout old_layout = vkTex->GetLayout();
if (old_layout == GSTextureVK::Layout::Undefined)
+2 -2
View File
@@ -50,7 +50,7 @@ public:
void* GetNativeHandle() const override;
bool Update(const GSVector4i& r, const void* data, int pitch, int layer = 0) override;
bool DoUpdate(const GSVector4i& r, const void* data, int pitch, int layer = 0) override;
bool Map(GSMap& m, const GSVector4i* r = NULL, int layer = 0) override;
void Unmap() override;
void GenerateMipmap() override;
@@ -117,7 +117,7 @@ public:
static std::unique_ptr<GSDownloadTextureVK> Create(u32 width, u32 height, GSTexture::Format format);
void CopyFromTexture(
void DoCopyFromTexture(
const GSVector4i& drc, GSTexture* stex, const GSVector4i& src, u32 src_level, bool use_transfer_pitch) override;
bool Map(const GSVector4i& read_rc) override;