summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorReinUsesLisp <reinuseslisp@airmail.cc>2019-04-24 07:45:03 +0200
committerReinUsesLisp <reinuseslisp@airmail.cc>2019-06-21 02:36:11 +0200
commit5f3aacdc3760f0e9e0daeda3ee4c55e42fc9397e (patch)
tree6c95776e1de8c347544745df1784424cafaeb414 /src
parenttexture_cache: Flush 3D textures in the order they are drawn (diff)
downloadyuzu-5f3aacdc3760f0e9e0daeda3ee4c55e42fc9397e.tar
yuzu-5f3aacdc3760f0e9e0daeda3ee4c55e42fc9397e.tar.gz
yuzu-5f3aacdc3760f0e9e0daeda3ee4c55e42fc9397e.tar.bz2
yuzu-5f3aacdc3760f0e9e0daeda3ee4c55e42fc9397e.tar.lz
yuzu-5f3aacdc3760f0e9e0daeda3ee4c55e42fc9397e.tar.xz
yuzu-5f3aacdc3760f0e9e0daeda3ee4c55e42fc9397e.tar.zst
yuzu-5f3aacdc3760f0e9e0daeda3ee4c55e42fc9397e.zip
Diffstat (limited to 'src')
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.cpp114
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.h7
-rw-r--r--src/video_core/texture_cache.cpp110
-rw-r--r--src/video_core/texture_cache.h161
4 files changed, 211 insertions, 181 deletions
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp
index 362f4019c..3e2a1f53c 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp
@@ -15,7 +15,6 @@
namespace OpenGL {
-using Tegra::Texture::ConvertFromGuestToHost;
using Tegra::Texture::SwizzleSource;
using VideoCore::MortonSwizzleMode;
@@ -207,32 +206,6 @@ OGLTexture CreateTexture(const SurfaceParams& params, GLenum target, GLenum inte
return texture;
}
-void SwizzleFunc(MortonSwizzleMode mode, u8* memory, const SurfaceParams& params, u8* buffer,
- u32 level) {
- const u32 width{params.GetMipWidth(level)};
- const u32 height{params.GetMipHeight(level)};
- const u32 block_height{params.GetMipBlockHeight(level)};
- const u32 block_depth{params.GetMipBlockDepth(level)};
-
- std::size_t guest_offset{params.GetGuestMipmapLevelOffset(level)};
- if (params.IsLayered()) {
- std::size_t host_offset{0};
- const std::size_t guest_stride = params.GetGuestLayerSize();
- const std::size_t host_stride = params.GetHostLayerSize(level);
- for (u32 layer = 0; layer < params.GetNumLayers(); layer++) {
- MortonSwizzle(mode, params.GetPixelFormat(), width, block_height, height, block_depth,
- 1, params.GetTileWidthSpacing(), buffer + host_offset,
- memory + guest_offset);
- guest_offset += guest_stride;
- host_offset += host_stride;
- }
- } else {
- MortonSwizzle(mode, params.GetPixelFormat(), width, block_height, height, block_depth,
- params.GetMipDepth(level), params.GetTileWidthSpacing(), buffer,
- memory + guest_offset);
- }
-}
-
} // Anonymous namespace
CachedSurface::CachedSurface(TextureCacheOpenGL& texture_cache, const SurfaceParams& params)
@@ -245,54 +218,11 @@ CachedSurface::CachedSurface(TextureCacheOpenGL& texture_cache, const SurfacePar
is_compressed = tuple.compressed;
target = GetTextureTarget(params);
texture = CreateTexture(params, target, internal_format);
- staging_buffer.resize(params.GetHostSizeInBytes());
}
CachedSurface::~CachedSurface() = default;
-void CachedSurface::LoadBuffer() {
- if (params.IsTiled()) {
- ASSERT_MSG(params.GetBlockWidth() == 1, "Block width is defined as {} on texture target {}",
- params.GetBlockWidth(), static_cast<u32>(params.GetTarget()));
- for (u32 level = 0; level < params.GetNumLevels(); ++level) {
- u8* const buffer{staging_buffer.data() + params.GetHostMipmapLevelOffset(level)};
- SwizzleFunc(MortonSwizzleMode::MortonToLinear, GetHostPtr(), params, buffer, level);
- }
- } else {
- ASSERT_MSG(params.GetNumLevels() == 1, "Linear mipmap loading is not implemented");
- const u32 bpp{GetFormatBpp(params.GetPixelFormat()) / CHAR_BIT};
- const u32 block_width{VideoCore::Surface::GetDefaultBlockWidth(params.GetPixelFormat())};
- const u32 block_height{VideoCore::Surface::GetDefaultBlockHeight(params.GetPixelFormat())};
- const u32 width{(params.GetWidth() + block_width - 1) / block_width};
- const u32 height{(params.GetHeight() + block_height - 1) / block_height};
- const u32 copy_size{width * bpp};
- if (params.GetPitch() == copy_size) {
- std::memcpy(staging_buffer.data(), GetHostPtr(), params.GetHostSizeInBytes());
- } else {
- const u8* start{GetHostPtr()};
- u8* write_to{staging_buffer.data()};
- for (u32 h = height; h > 0; --h) {
- std::memcpy(write_to, start, copy_size);
- start += params.GetPitch();
- write_to += copy_size;
- }
- }
- }
-
- for (u32 level = 0; level < params.GetNumLevels(); ++level) {
- ConvertFromGuestToHost(staging_buffer.data() + params.GetHostMipmapLevelOffset(level),
- params.GetPixelFormat(), params.GetMipWidth(level),
- params.GetMipHeight(level), params.GetMipDepth(level), true, true);
- }
-}
-
-void CachedSurface::FlushBufferImpl() {
- LOG_CRITICAL(Render_OpenGL, "Flushing");
-
- if (!IsModified()) {
- return;
- }
-
+void CachedSurface::DownloadTextureImpl() {
// TODO(Rodrigo): Optimize alignment
glPixelStorei(GL_PACK_ALIGNMENT, 1);
SCOPE_EXIT({ glPixelStorei(GL_PACK_ROW_LENGTH, 0); });
@@ -300,60 +230,30 @@ void CachedSurface::FlushBufferImpl() {
for (u32 level = 0; level < params.GetNumLevels(); ++level) {
glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(params.GetMipWidth(level)));
if (is_compressed) {
- glGetCompressedTextureImage(
- texture.handle, level, static_cast<GLsizei>(params.GetHostMipmapSize(level)),
- staging_buffer.data() + params.GetHostMipmapLevelOffset(level));
+ glGetCompressedTextureImage(texture.handle, level,
+ static_cast<GLsizei>(params.GetHostMipmapSize(level)),
+ GetStagingBufferLevelData(level));
} else {
glGetTextureImage(texture.handle, level, format, type,
static_cast<GLsizei>(params.GetHostMipmapSize(level)),
- staging_buffer.data() + params.GetHostMipmapLevelOffset(level));
+ GetStagingBufferLevelData(level));
}
}
-
- if (params.IsTiled()) {
- ASSERT_MSG(params.GetBlockWidth() == 1, "Block width is defined as {}",
- params.GetBlockWidth());
- for (u32 level = 0; level < params.GetNumLevels(); ++level) {
- u8* const buffer = staging_buffer.data() + params.GetHostMipmapLevelOffset(level);
- SwizzleFunc(MortonSwizzleMode::LinearToMorton, GetHostPtr(), params, buffer, level);
- }
- } else {
- UNIMPLEMENTED();
- /*
- ASSERT(params.GetTarget() == SurfaceTarget::Texture2D);
- ASSERT(params.GetNumLevels() == 1);
-
- const u32 bpp{params.GetFormatBpp() / 8};
- const u32 copy_size{params.GetWidth() * bpp};
- if (params.GetPitch() == copy_size) {
- std::memcpy(host_ptr, staging_buffer.data(), GetSizeInBytes());
- } else {
- u8* start{host_ptr};
- const u8* read_to{staging_buffer.data()};
- for (u32 h = params.GetHeight(); h > 0; --h) {
- std::memcpy(start, read_to, copy_size);
- start += params.GetPitch();
- read_to += copy_size;
- }
- }
- */
- }
}
void CachedSurface::UploadTextureImpl() {
+ SCOPE_EXIT({ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); });
for (u32 level = 0; level < params.GetNumLevels(); ++level) {
UploadTextureMipmap(level);
}
}
void CachedSurface::UploadTextureMipmap(u32 level) {
- u8* buffer{staging_buffer.data() + params.GetHostMipmapLevelOffset(level)};
-
// TODO(Rodrigo): Optimize alignment
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(params.GetMipWidth(level)));
- SCOPE_EXIT({ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); });
+ u8* buffer{GetStagingBufferLevelData(level)};
if (is_compressed) {
const auto image_size{static_cast<GLsizei>(params.GetHostMipmapSize(level))};
switch (params.GetTarget()) {
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h
index e6448c6f8..0a69be233 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.h
+++ b/src/video_core/renderer_opengl/gl_texture_cache.h
@@ -39,8 +39,6 @@ public:
explicit CachedSurface(TextureCacheOpenGL& texture_cache, const SurfaceParams& params);
~CachedSurface();
- void LoadBuffer();
-
GLenum GetTarget() const {
return target;
}
@@ -54,9 +52,8 @@ protected:
std::unique_ptr<CachedSurfaceView> CreateView(const ViewKey& view_key);
- void FlushBufferImpl();
-
void UploadTextureImpl();
+ void DownloadTextureImpl();
private:
void UploadTextureMipmap(u32 level);
@@ -68,8 +65,6 @@ private:
GLenum target{};
OGLTexture texture;
-
- std::vector<u8> staging_buffer;
};
class CachedSurfaceView final {
diff --git a/src/video_core/texture_cache.cpp b/src/video_core/texture_cache.cpp
index b78a7d951..146e8ed9b 100644
--- a/src/video_core/texture_cache.cpp
+++ b/src/video_core/texture_cache.cpp
@@ -7,14 +7,16 @@
#include "common/cityhash.h"
#include "common/common_types.h"
#include "core/core.h"
+#include "video_core/morton.h"
#include "video_core/surface.h"
#include "video_core/texture_cache.h"
+#include "video_core/textures/convert.h"
#include "video_core/textures/decoders.h"
#include "video_core/textures/texture.h"
namespace VideoCommon {
-using VideoCore::Surface::SurfaceTarget;
+using VideoCore::MortonSwizzleMode;
using VideoCore::Surface::ComponentTypeFromDepthFormat;
using VideoCore::Surface::ComponentTypeFromRenderTarget;
@@ -22,12 +24,118 @@ using VideoCore::Surface::ComponentTypeFromTexture;
using VideoCore::Surface::PixelFormatFromDepthFormat;
using VideoCore::Surface::PixelFormatFromRenderTargetFormat;
using VideoCore::Surface::PixelFormatFromTextureFormat;
+using VideoCore::Surface::SurfaceTarget;
using VideoCore::Surface::SurfaceTargetFromTextureType;
+using Tegra::Texture::ConvertFromGuestToHost;
+
+namespace {
+
constexpr u32 GetMipmapSize(bool uncompressed, u32 mip_size, u32 tile) {
return uncompressed ? mip_size : std::max(1U, (mip_size + tile - 1) / tile);
}
+void SwizzleFunc(MortonSwizzleMode mode, u8* memory, const SurfaceParams& params, u8* buffer,
+ u32 level) {
+ const u32 width{params.GetMipWidth(level)};
+ const u32 height{params.GetMipHeight(level)};
+ const u32 block_height{params.GetMipBlockHeight(level)};
+ const u32 block_depth{params.GetMipBlockDepth(level)};
+
+ std::size_t guest_offset{params.GetGuestMipmapLevelOffset(level)};
+ if (params.IsLayered()) {
+ std::size_t host_offset{0};
+ const std::size_t guest_stride = params.GetGuestLayerSize();
+ const std::size_t host_stride = params.GetHostLayerSize(level);
+ for (u32 layer = 0; layer < params.GetNumLayers(); layer++) {
+ MortonSwizzle(mode, params.GetPixelFormat(), width, block_height, height, block_depth,
+ 1, params.GetTileWidthSpacing(), buffer + host_offset,
+ memory + guest_offset);
+ guest_offset += guest_stride;
+ host_offset += host_stride;
+ }
+ } else {
+ MortonSwizzle(mode, params.GetPixelFormat(), width, block_height, height, block_depth,
+ params.GetMipDepth(level), params.GetTileWidthSpacing(), buffer,
+ memory + guest_offset);
+ }
+}
+
+} // Anonymous namespace
+
+SurfaceBaseImpl::SurfaceBaseImpl(const SurfaceParams& params) : params{params} {
+ staging_buffer.resize(params.GetHostSizeInBytes());
+}
+
+SurfaceBaseImpl::~SurfaceBaseImpl() = default;
+
+void SurfaceBaseImpl::LoadBuffer() {
+ if (params.IsTiled()) {
+ ASSERT_MSG(params.GetBlockWidth() == 1, "Block width is defined as {} on texture target {}",
+ params.GetBlockWidth(), static_cast<u32>(params.GetTarget()));
+ for (u32 level = 0; level < params.GetNumLevels(); ++level) {
+ u8* const buffer{GetStagingBufferLevelData(level)};
+ SwizzleFunc(MortonSwizzleMode::MortonToLinear, host_ptr, params, buffer, level);
+ }
+ } else {
+ ASSERT_MSG(params.GetNumLevels() == 1, "Linear mipmap loading is not implemented");
+ const u32 bpp{GetFormatBpp(params.GetPixelFormat()) / CHAR_BIT};
+ const u32 block_width{params.GetDefaultBlockWidth()};
+ const u32 block_height{params.GetDefaultBlockHeight()};
+ const u32 width{(params.GetWidth() + block_width - 1) / block_width};
+ const u32 height{(params.GetHeight() + block_height - 1) / block_height};
+ const u32 copy_size{width * bpp};
+ if (params.GetPitch() == copy_size) {
+ std::memcpy(staging_buffer.data(), host_ptr, params.GetHostSizeInBytes());
+ } else {
+ const u8* start{host_ptr};
+ u8* write_to{staging_buffer.data()};
+ for (u32 h = height; h > 0; --h) {
+ std::memcpy(write_to, start, copy_size);
+ start += params.GetPitch();
+ write_to += copy_size;
+ }
+ }
+ }
+
+ for (u32 level = 0; level < params.GetNumLevels(); ++level) {
+ ConvertFromGuestToHost(GetStagingBufferLevelData(level), params.GetPixelFormat(),
+ params.GetMipWidth(level), params.GetMipHeight(level),
+ params.GetMipDepth(level), true, true);
+ }
+}
+
+void SurfaceBaseImpl::FlushBuffer() {
+ if (params.IsTiled()) {
+ ASSERT_MSG(params.GetBlockWidth() == 1, "Block width is defined as {}",
+ params.GetBlockWidth());
+ for (u32 level = 0; level < params.GetNumLevels(); ++level) {
+ u8* const buffer = GetStagingBufferLevelData(level);
+ SwizzleFunc(MortonSwizzleMode::LinearToMorton, GetHostPtr(), params, buffer, level);
+ }
+ } else {
+ UNIMPLEMENTED();
+ /*
+ ASSERT(params.GetTarget() == SurfaceTarget::Texture2D);
+ ASSERT(params.GetNumLevels() == 1);
+
+ const u32 bpp{params.GetFormatBpp() / 8};
+ const u32 copy_size{params.GetWidth() * bpp};
+ if (params.GetPitch() == copy_size) {
+ std::memcpy(host_ptr, staging_buffer.data(), GetSizeInBytes());
+ } else {
+ u8* start{host_ptr};
+ const u8* read_to{staging_buffer.data()};
+ for (u32 h = params.GetHeight(); h > 0; --h) {
+ std::memcpy(start, read_to, copy_size);
+ start += params.GetPitch();
+ read_to += copy_size;
+ }
+ }
+ */
+ }
+}
+
SurfaceParams SurfaceParams::CreateForTexture(Core::System& system,
const Tegra::Texture::FullTextureInfo& config) {
SurfaceParams params;
diff --git a/src/video_core/texture_cache.h b/src/video_core/texture_cache.h
index f22e8e776..90c72cb15 100644
--- a/src/video_core/texture_cache.h
+++ b/src/video_core/texture_cache.h
@@ -273,37 +273,11 @@ struct hash<VideoCommon::ViewKey> {
namespace VideoCommon {
-template <typename TTextureCache, typename TView, typename TExecutionContext>
-class SurfaceBase {
- static_assert(std::is_trivially_copyable_v<TExecutionContext>);
-
+class SurfaceBaseImpl {
public:
- virtual void LoadBuffer() = 0;
-
- virtual TExecutionContext FlushBuffer(TExecutionContext exctx) = 0;
-
- virtual TExecutionContext UploadTexture(TExecutionContext exctx) = 0;
-
- TView* TryGetView(GPUVAddr view_addr, const SurfaceParams& view_params) {
- if (view_addr < gpu_addr || !params.IsFamiliar(view_params)) {
- // It can't be a view if it's in a prior address.
- return {};
- }
-
- const auto relative_offset{static_cast<u64>(view_addr - gpu_addr)};
- const auto it{view_offset_map.find(relative_offset)};
- if (it == view_offset_map.end()) {
- // Couldn't find an aligned view.
- return {};
- }
- const auto [layer, level] = it->second;
-
- if (!params.IsViewValid(view_params, layer, level)) {
- return {};
- }
+ void LoadBuffer();
- return GetView(layer, view_params.GetNumLayers(), level, view_params.GetNumLevels());
- }
+ void FlushBuffer();
GPUVAddr GetGpuAddr() const {
ASSERT(is_registered);
@@ -325,27 +299,10 @@ public:
return cache_addr;
}
- std::size_t GetSizeInBytes() const {
- return params.GetGuestSizeInBytes();
- }
-
- void MarkAsModified(bool is_modified_) {
- is_modified = is_modified_;
- if (is_modified_) {
- modification_tick = texture_cache.Tick();
- }
- }
-
const SurfaceParams& GetSurfaceParams() const {
return params;
}
- TView* GetView(GPUVAddr view_addr, const SurfaceParams& view_params) {
- TView* view{TryGetView(view_addr, view_params)};
- ASSERT(view != nullptr);
- return view;
- }
-
void Register(GPUVAddr gpu_addr_, VAddr cpu_addr_, u8* host_ptr_) {
ASSERT(!is_registered);
is_registered = true;
@@ -361,30 +318,95 @@ public:
is_registered = false;
}
- u64 GetModificationTick() const {
- return modification_tick;
- }
-
bool IsRegistered() const {
return is_registered;
}
-protected:
- explicit SurfaceBase(TTextureCache& texture_cache, const SurfaceParams& params)
- : params{params}, texture_cache{texture_cache}, view_offset_map{
- params.CreateViewOffsetMap()} {}
+ std::size_t GetSizeInBytes() const {
+ return params.GetGuestSizeInBytes();
+ }
- ~SurfaceBase() = default;
+ u8* GetStagingBufferLevelData(u32 level) {
+ return staging_buffer.data() + params.GetHostMipmapLevelOffset(level);
+ }
+
+protected:
+ explicit SurfaceBaseImpl(const SurfaceParams& params);
+ ~SurfaceBaseImpl(); // non-virtual is intended
virtual void DecorateSurfaceName() = 0;
- virtual std::unique_ptr<TView> CreateView(const ViewKey& view_key) = 0;
+ const SurfaceParams params;
+
+private:
+ GPUVAddr gpu_addr{};
+ VAddr cpu_addr{};
+ u8* host_ptr{};
+ CacheAddr cache_addr{};
+ bool is_registered{};
+
+ std::vector<u8> staging_buffer;
+};
+
+template <typename TTextureCache, typename TView, typename TExecutionContext>
+class SurfaceBase : public SurfaceBaseImpl {
+ static_assert(std::is_trivially_copyable_v<TExecutionContext>);
+
+public:
+ virtual TExecutionContext UploadTexture(TExecutionContext exctx) = 0;
+
+ virtual TExecutionContext DownloadTexture(TExecutionContext exctx) = 0;
+
+ TView* TryGetView(GPUVAddr view_addr, const SurfaceParams& view_params) {
+ if (view_addr < GetGpuAddr() || !params.IsFamiliar(view_params)) {
+ // It can't be a view if it's in a prior address.
+ return {};
+ }
+
+ const auto relative_offset{static_cast<u64>(view_addr - GetGpuAddr())};
+ const auto it{view_offset_map.find(relative_offset)};
+ if (it == view_offset_map.end()) {
+ // Couldn't find an aligned view.
+ return {};
+ }
+ const auto [layer, level] = it->second;
+
+ if (!params.IsViewValid(view_params, layer, level)) {
+ return {};
+ }
+
+ return GetView(layer, view_params.GetNumLayers(), level, view_params.GetNumLevels());
+ }
+
+ void MarkAsModified(bool is_modified_) {
+ is_modified = is_modified_;
+ if (is_modified_) {
+ modification_tick = texture_cache.Tick();
+ }
+ }
+
+ TView* GetView(GPUVAddr view_addr, const SurfaceParams& view_params) {
+ TView* view{TryGetView(view_addr, view_params)};
+ ASSERT(view != nullptr);
+ return view;
+ }
bool IsModified() const {
return is_modified;
}
- const SurfaceParams params;
+ u64 GetModificationTick() const {
+ return modification_tick;
+ }
+
+protected:
+ explicit SurfaceBase(TTextureCache& texture_cache, const SurfaceParams& params)
+ : SurfaceBaseImpl{params}, texture_cache{texture_cache},
+ view_offset_map{params.CreateViewOffsetMap()} {}
+
+ ~SurfaceBase() = default;
+
+ virtual std::unique_ptr<TView> CreateView(const ViewKey& view_key) = 0;
private:
TView* GetView(u32 base_layer, u32 num_layers, u32 base_level, u32 num_levels) {
@@ -400,13 +422,8 @@ private:
TTextureCache& texture_cache;
const std::map<u64, std::pair<u32, u32>> view_offset_map;
- GPUVAddr gpu_addr{};
- VAddr cpu_addr{};
- u8* host_ptr{};
- CacheAddr cache_addr{};
- u64 modification_tick{};
bool is_modified{};
- bool is_registered{};
+ u64 modification_tick{};
std::unordered_map<ViewKey, std::unique_ptr<TView>> views;
};
@@ -560,7 +577,7 @@ private:
if (!fast_view) {
// Flush even when we don't care about the contents, to preserve memory not
// written by the new surface.
- exctx = surface->FlushBuffer(exctx);
+ exctx = FlushSurface(exctx, surface);
}
Unregister(surface);
}
@@ -590,6 +607,16 @@ private:
return exctx;
}
+ TExecutionContext FlushSurface(TExecutionContext exctx,
+ const std::shared_ptr<TSurface>& surface) {
+ if (!surface->IsModified()) {
+ return exctx;
+ }
+ exctx = surface->DownloadTexture(exctx);
+ surface->FlushBuffer();
+ return exctx;
+ }
+
std::vector<std::shared_ptr<TSurface>> GetSurfacesInRegion(CacheAddr cache_addr,
std::size_t size) const {
if (size == 0) {
@@ -701,8 +728,8 @@ private:
template <typename TTextureCache, typename TView>
class SurfaceBaseContextless : public SurfaceBase<TTextureCache, TView, DummyExecutionContext> {
public:
- DummyExecutionContext FlushBuffer(DummyExecutionContext) {
- FlushBufferImpl();
+ DummyExecutionContext DownloadTexture(DummyExecutionContext) {
+ DownloadTextureImpl();
return {};
}
@@ -715,7 +742,7 @@ protected:
explicit SurfaceBaseContextless(TTextureCache& texture_cache, const SurfaceParams& params)
: SurfaceBase<TTextureCache, TView, DummyExecutionContext>{texture_cache, params} {}
- virtual void FlushBufferImpl() = 0;
+ virtual void DownloadTextureImpl() = 0;
virtual void UploadTextureImpl() = 0;
};