mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
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:
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user