diff --git a/Source/Core/Common/Common.vcxproj b/Source/Core/Common/Common.vcxproj
index 15418f6b29..8a40dc7c58 100644
--- a/Source/Core/Common/Common.vcxproj
+++ b/Source/Core/Common/Common.vcxproj
@@ -93,6 +93,7 @@
+
@@ -225,4 +226,4 @@
-
+
\ No newline at end of file
diff --git a/Source/Core/Common/Common.vcxproj.filters b/Source/Core/Common/Common.vcxproj.filters
index 4cd0ac4af8..ee4f949251 100644
--- a/Source/Core/Common/Common.vcxproj.filters
+++ b/Source/Core/Common/Common.vcxproj.filters
@@ -248,6 +248,9 @@
GL\GLExtensions
+
+ GL\GLExtensions
+
@@ -321,4 +324,4 @@
-
+
\ No newline at end of file
diff --git a/Source/Core/Common/GL/GLExtensions/EXT_texture_compression_s3tc.h b/Source/Core/Common/GL/GLExtensions/EXT_texture_compression_s3tc.h
new file mode 100644
index 0000000000..5eb17791c6
--- /dev/null
+++ b/Source/Core/Common/GL/GLExtensions/EXT_texture_compression_s3tc.h
@@ -0,0 +1,29 @@
+/*
+** Copyright (c) 2013-2015 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+#include "Common/GL/GLExtensions/gl_common.h"
+
+#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0
+#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1
+#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2
+#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3
diff --git a/Source/Core/Common/GL/GLExtensions/GLExtensions.h b/Source/Core/Common/GL/GLExtensions/GLExtensions.h
index 49e9fc282f..7745d78e60 100644
--- a/Source/Core/Common/GL/GLExtensions/GLExtensions.h
+++ b/Source/Core/Common/GL/GLExtensions/GLExtensions.h
@@ -31,6 +31,7 @@
#include "Common/GL/GLExtensions/ARB_uniform_buffer_object.h"
#include "Common/GL/GLExtensions/ARB_vertex_array_object.h"
#include "Common/GL/GLExtensions/ARB_viewport_array.h"
+#include "Common/GL/GLExtensions/EXT_texture_compression_s3tc.h"
#include "Common/GL/GLExtensions/EXT_texture_filter_anisotropic.h"
#include "Common/GL/GLExtensions/HP_occlusion_test.h"
#include "Common/GL/GLExtensions/KHR_debug.h"
diff --git a/Source/Core/VideoBackends/D3D/D3DBase.cpp b/Source/Core/VideoBackends/D3D/D3DBase.cpp
index 5b4328cb7f..06ee82b450 100644
--- a/Source/Core/VideoBackends/D3D/D3DBase.cpp
+++ b/Source/Core/VideoBackends/D3D/D3DBase.cpp
@@ -239,6 +239,19 @@ D3D_FEATURE_LEVEL GetFeatureLevel(IDXGIAdapter* adapter)
return feat_level;
}
+static bool SupportsS3TCTextures(ID3D11Device* device)
+{
+ UINT bc1_support, bc2_support, bc3_support;
+ if (FAILED(device->CheckFormatSupport(DXGI_FORMAT_BC1_UNORM, &bc1_support)) ||
+ FAILED(device->CheckFormatSupport(DXGI_FORMAT_BC2_UNORM, &bc2_support)) ||
+ FAILED(device->CheckFormatSupport(DXGI_FORMAT_BC3_UNORM, &bc3_support)))
+ {
+ return false;
+ }
+
+ return ((bc1_support & bc2_support & bc3_support) & D3D11_FORMAT_SUPPORT_TEXTURE2D) != 0;
+}
+
HRESULT Create(HWND wnd)
{
hWnd = wnd;
@@ -427,6 +440,7 @@ HRESULT Create(HWND wnd)
UINT format_support;
device->CheckFormatSupport(DXGI_FORMAT_B8G8R8A8_UNORM, &format_support);
bgra_textures_supported = (format_support & D3D11_FORMAT_SUPPORT_TEXTURE2D) != 0;
+ g_Config.backend_info.bSupportsST3CTextures = SupportsS3TCTextures(device);
stateman = new StateManager;
return S_OK;
diff --git a/Source/Core/VideoBackends/D3D/TextureCache.cpp b/Source/Core/VideoBackends/D3D/TextureCache.cpp
index d6369f290f..cd737e7201 100644
--- a/Source/Core/VideoBackends/D3D/TextureCache.cpp
+++ b/Source/Core/VideoBackends/D3D/TextureCache.cpp
@@ -7,6 +7,7 @@
#include
#include
+#include "Common/Assert.h"
#include "Common/CommonTypes.h"
#include "Common/Logging/Log.h"
@@ -29,6 +30,25 @@ static std::unique_ptr g_encoder;
const size_t MAX_COPY_BUFFERS = 32;
ID3D11Buffer* efbcopycbuf[MAX_COPY_BUFFERS] = {0};
+static DXGI_FORMAT GetDXGIFormatForHostFormat(HostTextureFormat format)
+{
+ switch (format)
+ {
+ case HostTextureFormat::DXT1:
+ return DXGI_FORMAT_BC1_UNORM;
+
+ case HostTextureFormat::DXT3:
+ return DXGI_FORMAT_BC2_UNORM;
+
+ case HostTextureFormat::DXT5:
+ return DXGI_FORMAT_BC3_UNORM;
+
+ case HostTextureFormat::RGBA8:
+ default:
+ return DXGI_FORMAT_R8G8B8A8_UNORM;
+ }
+}
+
TextureCache::TCacheEntry::~TCacheEntry()
{
texture->Release();
@@ -41,6 +61,11 @@ void TextureCache::TCacheEntry::Bind(unsigned int stage)
bool TextureCache::TCacheEntry::Save(const std::string& filename, unsigned int level)
{
+ // 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_(config.format == HostTextureFormat::RGBA8);
+
// Create a staging/readback texture with the dimensions of the specified mip level.
u32 mip_width = std::max(config.width >> level, 1u);
u32 mip_height = std::max(config.height >> level, 1u);
@@ -129,28 +154,30 @@ void TextureCache::TCacheEntry::CopyRectangleFromTexture(const TCacheEntryBase*
g_renderer->RestoreAPIState();
}
-void TextureCache::TCacheEntry::Load(const u8* buffer, u32 width, u32 height, u32 expanded_width,
- u32 level)
+void TextureCache::TCacheEntry::Load(u32 level, u32 width, u32 height, u32 row_length,
+ const u8* buffer, size_t buffer_size)
{
- unsigned int src_pitch = 4 * expanded_width;
- D3D::context->UpdateSubresource(texture->GetTex(), level, nullptr, buffer, src_pitch, 0);
+ size_t src_pitch = CalculateHostTextureLevelPitch(config.format, row_length);
+ D3D::context->UpdateSubresource(texture->GetTex(), level, nullptr, buffer,
+ static_cast(src_pitch), 0);
}
TextureCacheBase::TCacheEntryBase* TextureCache::CreateTexture(const TCacheEntryConfig& config)
{
+ DXGI_FORMAT dxgi_format = GetDXGIFormatForHostFormat(config.format);
if (config.rendertarget)
{
return new TCacheEntry(
- config, D3DTexture2D::Create(
- config.width, config.height, (D3D11_BIND_FLAG)((int)D3D11_BIND_RENDER_TARGET |
- (int)D3D11_BIND_SHADER_RESOURCE),
- D3D11_USAGE_DEFAULT, DXGI_FORMAT_R8G8B8A8_UNORM, 1, config.layers));
+ config, D3DTexture2D::Create(config.width, config.height,
+ (D3D11_BIND_FLAG)((int)D3D11_BIND_RENDER_TARGET |
+ (int)D3D11_BIND_SHADER_RESOURCE),
+ D3D11_USAGE_DEFAULT, dxgi_format, 1, config.layers));
}
else
{
const D3D11_TEXTURE2D_DESC texdesc =
- CD3D11_TEXTURE2D_DESC(DXGI_FORMAT_R8G8B8A8_UNORM, config.width, config.height, 1,
- config.levels, D3D11_BIND_SHADER_RESOURCE, D3D11_USAGE_DEFAULT, 0);
+ CD3D11_TEXTURE2D_DESC(dxgi_format, config.width, config.height, 1, config.levels,
+ D3D11_BIND_SHADER_RESOURCE, D3D11_USAGE_DEFAULT, 0);
ID3D11Texture2D* pTexture;
const HRESULT hr = D3D::device->CreateTexture2D(&texdesc, nullptr, &pTexture);
diff --git a/Source/Core/VideoBackends/D3D/TextureCache.h b/Source/Core/VideoBackends/D3D/TextureCache.h
index 01b229cf72..78450eefdd 100644
--- a/Source/Core/VideoBackends/D3D/TextureCache.h
+++ b/Source/Core/VideoBackends/D3D/TextureCache.h
@@ -30,7 +30,8 @@ private:
const MathUtil::Rectangle& srcrect,
const MathUtil::Rectangle& dstrect) override;
- void Load(const u8* buffer, u32 width, u32 height, u32 expanded_width, u32 levels) override;
+ void Load(u32 level, u32 width, u32 height, u32 row_length, const u8* buffer,
+ size_t buffer_size) override;
void FromRenderTarget(bool is_depth_copy, const EFBRectangle& srcRect, bool scaleByHalf,
unsigned int cbufid, const float* colmat) override;
diff --git a/Source/Core/VideoBackends/D3D/main.cpp b/Source/Core/VideoBackends/D3D/main.cpp
index 48c7dd8391..5cc05f2af9 100644
--- a/Source/Core/VideoBackends/D3D/main.cpp
+++ b/Source/Core/VideoBackends/D3D/main.cpp
@@ -77,6 +77,7 @@ void VideoBackend::InitBackendInfo()
g_Config.backend_info.bSupportsMultithreading = false;
g_Config.backend_info.bSupportsInternalResolutionFrameDumps = false;
g_Config.backend_info.bSupportsGPUTextureDecoding = false;
+ g_Config.backend_info.bSupportsST3CTextures = false;
IDXGIFactory* factory;
IDXGIAdapter* ad;
diff --git a/Source/Core/VideoBackends/D3D12/D3DBase.cpp b/Source/Core/VideoBackends/D3D12/D3DBase.cpp
index 9f1da4042e..56c5bc6534 100644
--- a/Source/Core/VideoBackends/D3D12/D3DBase.cpp
+++ b/Source/Core/VideoBackends/D3D12/D3DBase.cpp
@@ -262,6 +262,21 @@ std::vector EnumAAModes(ID3D12Device* device)
return aa_modes;
}
+static bool SupportsS3TCTextures(ID3D12Device* device)
+{
+ auto CheckForFormat = [](ID3D12Device* device, DXGI_FORMAT format) {
+ D3D12_FEATURE_DATA_FORMAT_SUPPORT data = {format};
+ if (FAILED(device->CheckFeatureSupport(D3D12_FEATURE_FORMAT_SUPPORT, &data, sizeof(data))))
+ return false;
+
+ return (data.Support1 & D3D12_FORMAT_SUPPORT1_TEXTURE2D) != 0;
+ };
+
+ return CheckForFormat(device, DXGI_FORMAT_BC1_UNORM) &&
+ CheckForFormat(device, DXGI_FORMAT_BC2_UNORM) &&
+ CheckForFormat(device, DXGI_FORMAT_BC3_UNORM);
+}
+
HRESULT Create(HWND wnd)
{
hWnd = wnd;
@@ -478,6 +493,8 @@ HRESULT Create(HWND wnd)
SAFE_RELEASE(factory);
SAFE_RELEASE(adapter);
+ g_Config.backend_info.bSupportsST3CTextures = SupportsS3TCTextures(device12);
+
return S_OK;
}
diff --git a/Source/Core/VideoBackends/D3D12/TextureCache.cpp b/Source/Core/VideoBackends/D3D12/TextureCache.cpp
index d626cb7732..4b0a47b158 100644
--- a/Source/Core/VideoBackends/D3D12/TextureCache.cpp
+++ b/Source/Core/VideoBackends/D3D12/TextureCache.cpp
@@ -33,6 +33,25 @@ static u32 s_efb_copy_last_cbuf_id = UINT_MAX;
static ID3D12Resource* s_texture_cache_entry_readback_buffer = nullptr;
static size_t s_texture_cache_entry_readback_buffer_size = 0;
+static DXGI_FORMAT GetDXGIFormatForHostFormat(HostTextureFormat format)
+{
+ switch (format)
+ {
+ case HostTextureFormat::DXT1:
+ return DXGI_FORMAT_BC1_UNORM;
+
+ case HostTextureFormat::DXT3:
+ return DXGI_FORMAT_BC2_UNORM;
+
+ case HostTextureFormat::DXT5:
+ return DXGI_FORMAT_BC3_UNORM;
+
+ case HostTextureFormat::RGBA8:
+ default:
+ return DXGI_FORMAT_R8G8B8A8_UNORM;
+ }
+}
+
TextureCache::TCacheEntry::~TCacheEntry()
{
m_texture->Release();
@@ -51,6 +70,11 @@ bool TextureCache::TCacheEntry::Save(const std::string& filename, unsigned int l
Common::AlignUp(level_width * sizeof(u32), D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
size_t required_readback_buffer_size = level_pitch * level_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_(config.format == HostTextureFormat::RGBA8);
+
// Check if the current readback buffer is large enough
if (required_readback_buffer_size > s_texture_cache_entry_readback_buffer_size)
{
@@ -175,22 +199,24 @@ void TextureCache::TCacheEntry::CopyRectangleFromTexture(const TCacheEntryBase*
g_renderer->RestoreAPIState();
}
-void TextureCache::TCacheEntry::Load(const u8* buffer, u32 width, u32 height, u32 expanded_width,
- u32 level)
+void TextureCache::TCacheEntry::Load(u32 level, u32 width, u32 height, u32 row_length,
+ const u8* buffer, size_t buffer_size)
{
- unsigned int src_pitch = 4 * expanded_width;
- D3D::ReplaceRGBATexture2D(m_texture->GetTex12(), buffer, width, height, src_pitch, level,
+ size_t src_pitch = CalculateHostTextureLevelPitch(config.format, row_length);
+ D3D::ReplaceRGBATexture2D(m_texture->GetTex12(), buffer, width, height,
+ static_cast(src_pitch), level,
m_texture->GetResourceUsageState());
}
TextureCacheBase::TCacheEntryBase* TextureCache::CreateTexture(const TCacheEntryConfig& config)
{
+ DXGI_FORMAT dxgi_format = GetDXGIFormatForHostFormat(config.format);
if (config.rendertarget)
{
D3DTexture2D* texture =
D3DTexture2D::Create(config.width, config.height,
TEXTURE_BIND_FLAG_SHADER_RESOURCE | TEXTURE_BIND_FLAG_RENDER_TARGET,
- DXGI_FORMAT_R8G8B8A8_UNORM, 1, config.layers);
+ dxgi_format, 1, config.layers);
TCacheEntry* entry = new TCacheEntry(config, texture);
@@ -204,8 +230,8 @@ TextureCacheBase::TCacheEntryBase* TextureCache::CreateTexture(const TCacheEntry
{
ID3D12Resource* texture_resource = nullptr;
- D3D12_RESOURCE_DESC texture_resource_desc = CD3DX12_RESOURCE_DESC::Tex2D(
- DXGI_FORMAT_R8G8B8A8_UNORM, config.width, config.height, 1, config.levels);
+ D3D12_RESOURCE_DESC texture_resource_desc =
+ CD3DX12_RESOURCE_DESC::Tex2D(dxgi_format, config.width, config.height, 1, config.levels);
CheckHR(D3D::device12->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT), D3D12_HEAP_FLAG_NONE,
diff --git a/Source/Core/VideoBackends/D3D12/TextureCache.h b/Source/Core/VideoBackends/D3D12/TextureCache.h
index f6921afe5b..fb8cdf3385 100644
--- a/Source/Core/VideoBackends/D3D12/TextureCache.h
+++ b/Source/Core/VideoBackends/D3D12/TextureCache.h
@@ -39,7 +39,8 @@ private:
const MathUtil::Rectangle& src_rect,
const MathUtil::Rectangle& dst_rect) override;
- void Load(const u8* buffer, u32 width, u32 height, u32 expanded_width, u32 levels) override;
+ void Load(u32 level, u32 width, u32 height, u32 row_length, const u8* buffer,
+ size_t buffer_size) override;
void FromRenderTarget(bool is_depth_copy, const EFBRectangle& src_rect, bool scale_by_half,
unsigned int cbuf_id, const float* colmat) override;
diff --git a/Source/Core/VideoBackends/D3D12/main.cpp b/Source/Core/VideoBackends/D3D12/main.cpp
index cb075fa071..d1ce50afb9 100644
--- a/Source/Core/VideoBackends/D3D12/main.cpp
+++ b/Source/Core/VideoBackends/D3D12/main.cpp
@@ -80,6 +80,7 @@ void VideoBackend::InitBackendInfo()
g_Config.backend_info.bSupportsMultithreading = false;
g_Config.backend_info.bSupportsInternalResolutionFrameDumps = false;
g_Config.backend_info.bSupportsGPUTextureDecoding = false;
+ g_Config.backend_info.bSupportsST3CTextures = false;
IDXGIFactory* factory;
IDXGIAdapter* ad;
diff --git a/Source/Core/VideoBackends/Null/NullBackend.cpp b/Source/Core/VideoBackends/Null/NullBackend.cpp
index 7892b86927..bcbbd1aaab 100644
--- a/Source/Core/VideoBackends/Null/NullBackend.cpp
+++ b/Source/Core/VideoBackends/Null/NullBackend.cpp
@@ -45,6 +45,7 @@ void VideoBackend::InitBackendInfo()
g_Config.backend_info.bSupportsMultithreading = false;
g_Config.backend_info.bSupportsInternalResolutionFrameDumps = false;
g_Config.backend_info.bSupportsGPUTextureDecoding = false;
+ g_Config.backend_info.bSupportsST3CTextures = false;
// aamodes: We only support 1 sample, so no MSAA
g_Config.backend_info.Adapters.clear();
diff --git a/Source/Core/VideoBackends/Null/TextureCache.h b/Source/Core/VideoBackends/Null/TextureCache.h
index 48d111dce6..399af04764 100644
--- a/Source/Core/VideoBackends/Null/TextureCache.h
+++ b/Source/Core/VideoBackends/Null/TextureCache.h
@@ -31,7 +31,10 @@ private:
{
TCacheEntry(const TCacheEntryConfig& _config) : TCacheEntryBase(_config) {}
~TCacheEntry() {}
- void Load(const u8* buffer, u32 width, u32 height, u32 expanded_width, u32 level) override {}
+ void Load(u32 level, u32 width, u32 height, u32 row_length, const u8* buffer,
+ size_t buffer_size) override
+ {
+ }
void FromRenderTarget(bool is_depth_copy, const EFBRectangle& src_rect, bool scale_by_half,
unsigned int cbufid, const float* colmat) override
{
diff --git a/Source/Core/VideoBackends/OGL/Render.cpp b/Source/Core/VideoBackends/OGL/Render.cpp
index ce58be68cc..360bc76a70 100644
--- a/Source/Core/VideoBackends/OGL/Render.cpp
+++ b/Source/Core/VideoBackends/OGL/Render.cpp
@@ -462,6 +462,8 @@ Renderer::Renderer()
g_ogl_config.bSupportsConservativeDepth = GLExtensions::Supports("GL_ARB_conservative_depth");
g_ogl_config.bSupportsAniso = GLExtensions::Supports("GL_EXT_texture_filter_anisotropic");
g_Config.backend_info.bSupportsComputeShaders = GLExtensions::Supports("GL_ARB_compute_shader");
+ g_Config.backend_info.bSupportsST3CTextures =
+ GLExtensions::Supports("GL_EXT_texture_compression_s3tc");
if (GLInterface->GetMode() == GLInterfaceMode::MODE_OPENGLES3)
{
diff --git a/Source/Core/VideoBackends/OGL/TextureCache.cpp b/Source/Core/VideoBackends/OGL/TextureCache.cpp
index 3bf1342809..2031d7e220 100644
--- a/Source/Core/VideoBackends/OGL/TextureCache.cpp
+++ b/Source/Core/VideoBackends/OGL/TextureCache.cpp
@@ -11,6 +11,7 @@
#include
#include
+#include "Common/Assert.h"
#include "Common/GL/GLInterfaceBase.h"
#include "Common/MsgHandler.h"
#include "Common/StringUtil.h"
@@ -87,6 +88,48 @@ bool SaveTexture(const std::string& filename, u32 textarget, u32 tex, int virtua
return TextureToPng(data.data(), width * 4, filename, width, height, true);
}
+static GLenum GetGLInternalFormatForTextureFormat(HostTextureFormat format, bool storage)
+{
+ switch (format)
+ {
+ case HostTextureFormat::DXT1:
+ return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
+ case HostTextureFormat::DXT3:
+ return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
+ case HostTextureFormat::DXT5:
+ return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
+ case HostTextureFormat::RGBA8:
+ default:
+ return storage ? GL_RGBA8 : GL_RGBA;
+ }
+}
+
+static GLenum GetGLFormatForTextureFormat(HostTextureFormat format)
+{
+ switch (format)
+ {
+ case HostTextureFormat::RGBA8:
+ return GL_RGBA;
+
+ // Compressed texture formats don't use this parameter.
+ default:
+ return GL_UNSIGNED_BYTE;
+ }
+}
+
+static GLenum GetGLTypeForTextureFormat(HostTextureFormat format)
+{
+ switch (format)
+ {
+ case HostTextureFormat::RGBA8:
+ return GL_UNSIGNED_BYTE;
+
+ // Compressed texture formats don't use this parameter.
+ default:
+ return GL_UNSIGNED_BYTE;
+ }
+}
+
TextureCache::TCacheEntry::~TCacheEntry()
{
if (texture)
@@ -129,6 +172,11 @@ void TextureCache::TCacheEntry::Bind(unsigned int stage)
bool TextureCache::TCacheEntry::Save(const std::string& filename, unsigned int level)
{
+ // 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_(config.format == HostTextureFormat::RGBA8);
+
return SaveTexture(filename, GL_TEXTURE_2D_ARRAY, texture, config.width, config.height, level);
}
@@ -143,12 +191,15 @@ TextureCache::TCacheEntryBase* TextureCache::CreateTexture(const TCacheEntryConf
if (g_ogl_config.bSupportsTextureStorage)
{
- glTexStorage3D(GL_TEXTURE_2D_ARRAY, config.levels, GL_RGBA8, config.width, config.height,
- config.layers);
+ GLenum gl_internal_format = GetGLInternalFormatForTextureFormat(config.format, true);
+ glTexStorage3D(GL_TEXTURE_2D_ARRAY, config.levels, gl_internal_format, config.width,
+ config.height, config.layers);
}
if (config.rendertarget)
{
+ // We can't render to compressed formats.
+ _assert_(!IsCompressedHostTextureFormat(config.format));
if (!g_ogl_config.bSupportsTextureStorage)
{
for (u32 level = 0; level < config.levels; level++)
@@ -202,8 +253,8 @@ void TextureCache::TCacheEntry::CopyRectangleFromTexture(const TCacheEntryBase*
g_renderer->RestoreAPIState();
}
-void TextureCache::TCacheEntry::Load(const u8* buffer, u32 width, u32 height, u32 expanded_width,
- u32 level)
+void TextureCache::TCacheEntry::Load(u32 level, u32 width, u32 height, u32 row_length,
+ const u8* buffer, size_t buffer_size)
{
if (level >= config.levels)
PanicAlert("Texture only has %d levels, can't update level %d", config.levels, level);
@@ -216,21 +267,40 @@ void TextureCache::TCacheEntry::Load(const u8* buffer, u32 width, u32 height, u3
glActiveTexture(GL_TEXTURE9);
glBindTexture(GL_TEXTURE_2D_ARRAY, texture);
- if (expanded_width != width)
- glPixelStorei(GL_UNPACK_ROW_LENGTH, expanded_width);
+ if (row_length != width)
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, row_length);
- if (g_ogl_config.bSupportsTextureStorage)
+ GLenum gl_internal_format = GetGLInternalFormatForTextureFormat(config.format, false);
+ if (IsCompressedHostTextureFormat(config.format))
{
- glTexSubImage3D(GL_TEXTURE_2D_ARRAY, level, 0, 0, 0, width, height, 1, GL_RGBA,
- GL_UNSIGNED_BYTE, buffer);
+ if (g_ogl_config.bSupportsTextureStorage)
+ {
+ glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, level, 0, 0, 0, width, height, 1,
+ gl_internal_format, static_cast(buffer_size), buffer);
+ }
+ else
+ {
+ glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, level, gl_internal_format, width, height, 1, 0,
+ static_cast(buffer_size), buffer);
+ }
}
else
{
- glTexImage3D(GL_TEXTURE_2D_ARRAY, level, GL_RGBA, width, height, 1, 0, GL_RGBA,
- GL_UNSIGNED_BYTE, buffer);
+ GLenum gl_format = GetGLFormatForTextureFormat(config.format);
+ GLenum gl_type = GetGLTypeForTextureFormat(config.format);
+ if (g_ogl_config.bSupportsTextureStorage)
+ {
+ glTexSubImage3D(GL_TEXTURE_2D_ARRAY, level, 0, 0, 0, width, height, 1, gl_format, gl_type,
+ buffer);
+ }
+ else
+ {
+ glTexImage3D(GL_TEXTURE_2D_ARRAY, level, gl_internal_format, width, height, 1, 0, gl_format,
+ gl_type, buffer);
+ }
}
- if (expanded_width != width)
+ if (row_length != width)
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
TextureCache::SetStage();
diff --git a/Source/Core/VideoBackends/OGL/TextureCache.h b/Source/Core/VideoBackends/OGL/TextureCache.h
index aece35cd6c..de0de485f5 100644
--- a/Source/Core/VideoBackends/OGL/TextureCache.h
+++ b/Source/Core/VideoBackends/OGL/TextureCache.h
@@ -45,7 +45,8 @@ private:
const MathUtil::Rectangle& srcrect,
const MathUtil::Rectangle& dstrect) override;
- void Load(const u8* buffer, u32 width, u32 height, u32 expanded_width, u32 level) override;
+ void Load(u32 level, u32 width, u32 height, u32 row_length, const u8* buffer,
+ size_t buffer_size) override;
void FromRenderTarget(bool is_depth_copy, const EFBRectangle& srcRect, bool scaleByHalf,
unsigned int cbufid, const float* colmat) override;
diff --git a/Source/Core/VideoBackends/OGL/main.cpp b/Source/Core/VideoBackends/OGL/main.cpp
index 430859932e..f9bf7c069e 100644
--- a/Source/Core/VideoBackends/OGL/main.cpp
+++ b/Source/Core/VideoBackends/OGL/main.cpp
@@ -103,6 +103,7 @@ void VideoBackend::InitBackendInfo()
g_Config.backend_info.bSupportsPaletteConversion = true;
g_Config.backend_info.bSupportsClipControl = true;
g_Config.backend_info.bSupportsDepthClamp = true;
+ g_Config.backend_info.bSupportsST3CTextures = false;
g_Config.backend_info.Adapters.clear();
diff --git a/Source/Core/VideoBackends/Software/SWmain.cpp b/Source/Core/VideoBackends/Software/SWmain.cpp
index 0f67c0e6fd..e004a0cac3 100644
--- a/Source/Core/VideoBackends/Software/SWmain.cpp
+++ b/Source/Core/VideoBackends/Software/SWmain.cpp
@@ -65,7 +65,10 @@ private:
{
TCacheEntry(const TCacheEntryConfig& _config) : TCacheEntryBase(_config) {}
~TCacheEntry() {}
- void Load(const u8* buffer, u32 width, u32 height, u32 expanded_width, u32 level) override {}
+ void Load(u32 level, u32 width, u32 height, u32 row_length, const u8* buffer,
+ size_t buffer_size) override
+ {
+ }
void FromRenderTarget(bool is_depth_copy, const EFBRectangle& srcRect, bool scaleByHalf,
unsigned int cbufid, const float* colmat) override
{
@@ -134,6 +137,7 @@ void VideoSoftware::InitBackendInfo()
g_Config.backend_info.bSupportsComputeShaders = false;
g_Config.backend_info.bSupportsInternalResolutionFrameDumps = false;
g_Config.backend_info.bSupportsGPUTextureDecoding = false;
+ g_Config.backend_info.bSupportsST3CTextures = false;
// aamodes
g_Config.backend_info.AAModes = {1};
diff --git a/Source/Core/VideoBackends/Vulkan/Constants.h b/Source/Core/VideoBackends/Vulkan/Constants.h
index 7b6b6d3583..9d1ab3fecd 100644
--- a/Source/Core/VideoBackends/Vulkan/Constants.h
+++ b/Source/Core/VideoBackends/Vulkan/Constants.h
@@ -109,7 +109,7 @@ constexpr size_t MAXIMUM_TEXTURE_UPLOAD_BUFFER_SIZE = 64 * 1024 * 1024;
// streaming buffer and be blocking frequently. Games are unlikely to have textures this
// large anyway, so it's only really an issue for HD texture packs, and memory is not
// a limiting factor in these scenarios anyway.
-constexpr size_t STAGING_TEXTURE_UPLOAD_THRESHOLD = 1024 * 1024 * 4;
+constexpr size_t STAGING_TEXTURE_UPLOAD_THRESHOLD = 1024 * 1024 * 8;
// Streaming uniform buffer size
constexpr size_t INITIAL_UNIFORM_STREAM_BUFFER_SIZE = 16 * 1024 * 1024;
diff --git a/Source/Core/VideoBackends/Vulkan/StagingBuffer.cpp b/Source/Core/VideoBackends/Vulkan/StagingBuffer.cpp
index 6bd8170fc9..6924f41eca 100644
--- a/Source/Core/VideoBackends/Vulkan/StagingBuffer.cpp
+++ b/Source/Core/VideoBackends/Vulkan/StagingBuffer.cpp
@@ -127,7 +127,7 @@ void StagingBuffer::InvalidateCPUCache(VkDeviceSize offset, VkDeviceSize size)
void StagingBuffer::Read(VkDeviceSize offset, void* data, size_t size, bool invalidate_caches)
{
_assert_((offset + size) <= m_size);
- _assert_(offset >= m_map_offset && size < (m_map_size + (offset - m_map_offset)));
+ _assert_(offset >= m_map_offset && size <= (m_map_size + (offset - m_map_offset)));
if (invalidate_caches)
InvalidateCPUCache(offset, size);
@@ -138,7 +138,7 @@ void StagingBuffer::Write(VkDeviceSize offset, const void* data, size_t size,
bool invalidate_caches)
{
_assert_((offset + size) <= m_size);
- _assert_(offset >= m_map_offset && size < (m_map_size + (offset - m_map_offset)));
+ _assert_(offset >= m_map_offset && size <= (m_map_size + (offset - m_map_offset)));
memcpy(m_map_pointer + (offset - m_map_offset), data, size);
if (invalidate_caches)
diff --git a/Source/Core/VideoBackends/Vulkan/TextureCache.cpp b/Source/Core/VideoBackends/Vulkan/TextureCache.cpp
index 08c370b48f..67faf980ea 100644
--- a/Source/Core/VideoBackends/Vulkan/TextureCache.cpp
+++ b/Source/Core/VideoBackends/Vulkan/TextureCache.cpp
@@ -9,6 +9,7 @@
#include
#include
+#include "Common/Align.h"
#include "Common/Assert.h"
#include "Common/CommonFuncs.h"
#include "Common/Logging/Log.h"
@@ -238,9 +239,10 @@ TextureCacheBase::TCacheEntryBase* TextureCache::CreateTexture(const TCacheEntry
usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
// Allocate texture object
+ VkFormat vk_format = Util::GetVkFormatForHostTextureFormat(config.format);
std::unique_ptr texture = Texture2D::Create(
- config.width, config.height, config.levels, config.layers, TEXTURECACHE_TEXTURE_FORMAT,
- VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_VIEW_TYPE_2D_ARRAY, VK_IMAGE_TILING_OPTIMAL, usage);
+ config.width, config.height, config.levels, config.layers, vk_format, VK_SAMPLE_COUNT_1_BIT,
+ VK_IMAGE_VIEW_TYPE_2D_ARRAY, VK_IMAGE_TILING_OPTIMAL, usage);
if (!texture)
return nullptr;
@@ -341,8 +343,8 @@ TextureCache::TCacheEntry::~TCacheEntry()
g_command_buffer_mgr->DeferFramebufferDestruction(m_framebuffer);
}
-void TextureCache::TCacheEntry::Load(const u8* buffer, unsigned int width, unsigned int height,
- unsigned int expanded_width, unsigned int level)
+void TextureCache::TCacheEntry::Load(u32 level, u32 width, u32 height, u32 row_length,
+ const u8* buffer, size_t buffer_size)
{
// Can't copy data larger than the texture extents.
width = std::max(1u, std::min(width, m_texture->GetWidth() >> level));
@@ -366,87 +368,68 @@ void TextureCache::TCacheEntry::Load(const u8* buffer, unsigned int width, unsig
m_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentInitCommandBuffer(),
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
- // Does this texture data fit within the streaming buffer?
- u32 upload_width = width;
- u32 upload_pitch = upload_width * sizeof(u32);
- u32 upload_size = upload_pitch * height;
+ // For unaligned textures, we can save some memory in the transfer buffer by skipping the rows
+ // that lie outside of the texture's dimensions.
u32 upload_alignment = static_cast(g_vulkan_context->GetBufferImageGranularity());
- u32 source_pitch = expanded_width * 4;
- if ((upload_size + upload_alignment) <= STAGING_TEXTURE_UPLOAD_THRESHOLD &&
- (upload_size + upload_alignment) <= MAXIMUM_TEXTURE_UPLOAD_BUFFER_SIZE)
- {
- // Assume tightly packed rows, with no padding as the buffer source.
- StreamBuffer* upload_buffer = TextureCache::GetInstance()->m_texture_upload_buffer.get();
+ u32 block_size = Util::GetBlockSize(m_texture->GetFormat());
+ u32 num_rows = Common::AlignUp(height, block_size) / block_size;
+ size_t source_pitch = CalculateHostTextureLevelPitch(config.format, row_length);
+ size_t upload_size = source_pitch * num_rows;
+ std::unique_ptr temp_buffer;
+ VkBuffer upload_buffer;
+ VkDeviceSize upload_buffer_offset;
- // Allocate memory from the streaming buffer for the texture data.
- if (!upload_buffer->ReserveMemory(upload_size, g_vulkan_context->GetBufferImageGranularity()))
+ // Does this texture data fit within the streaming buffer?
+ if (upload_size <= STAGING_TEXTURE_UPLOAD_THRESHOLD &&
+ upload_size <= MAXIMUM_TEXTURE_UPLOAD_BUFFER_SIZE)
+ {
+ StreamBuffer* stream_buffer = TextureCache::GetInstance()->m_texture_upload_buffer.get();
+ if (!stream_buffer->ReserveMemory(upload_size, upload_alignment))
{
// Execute the command buffer first.
WARN_LOG(VIDEO, "Executing command list while waiting for space in texture upload buffer");
Util::ExecuteCurrentCommandsAndRestoreState(false);
// Try allocating again. This may cause a fence wait.
- if (!upload_buffer->ReserveMemory(upload_size, g_vulkan_context->GetBufferImageGranularity()))
+ if (!stream_buffer->ReserveMemory(upload_size, upload_alignment))
PanicAlert("Failed to allocate space in texture upload buffer");
}
- // Grab buffer pointers
- VkBuffer image_upload_buffer = upload_buffer->GetBuffer();
- VkDeviceSize image_upload_buffer_offset = upload_buffer->GetCurrentOffset();
- u8* image_upload_buffer_pointer = upload_buffer->GetCurrentHostPointer();
-
- // Copy to the buffer using the stride from the subresource layout
- const u8* source_ptr = buffer;
- if (upload_pitch != source_pitch)
- {
- VkDeviceSize copy_pitch = std::min(source_pitch, upload_pitch);
- for (unsigned int row = 0; row < height; row++)
- {
- memcpy(image_upload_buffer_pointer + row * upload_pitch, source_ptr + row * source_pitch,
- copy_pitch);
- }
- }
- else
- {
- // Can copy the whole thing in one block, the pitch matches
- memcpy(image_upload_buffer_pointer, source_ptr, upload_size);
- }
-
- // Flush buffer memory if necessary
- upload_buffer->CommitMemory(upload_size);
-
- // Copy from the streaming buffer to the actual image.
- VkBufferImageCopy image_copy = {
- image_upload_buffer_offset, // VkDeviceSize bufferOffset
- 0, // uint32_t bufferRowLength
- 0, // uint32_t bufferImageHeight
- {VK_IMAGE_ASPECT_COLOR_BIT, level, 0, 1}, // VkImageSubresourceLayers imageSubresource
- {0, 0, 0}, // VkOffset3D imageOffset
- {width, height, 1} // VkExtent3D imageExtent
- };
- vkCmdCopyBufferToImage(g_command_buffer_mgr->GetCurrentInitCommandBuffer(), image_upload_buffer,
- m_texture->GetImage(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1,
- &image_copy);
+ // Copy to the streaming buffer.
+ upload_buffer = stream_buffer->GetBuffer();
+ upload_buffer_offset = stream_buffer->GetCurrentOffset();
+ std::memcpy(stream_buffer->GetCurrentHostPointer(), buffer, upload_size);
+ stream_buffer->CommitMemory(upload_size);
}
else
{
- // Slow path. The data for the image is too large to fit in the streaming buffer, so we need
- // to allocate a temporary texture to store the data in, then copy to the real texture.
- std::unique_ptr staging_texture = StagingTexture2D::Create(
- STAGING_BUFFER_TYPE_UPLOAD, width, height, TEXTURECACHE_TEXTURE_FORMAT);
-
- if (!staging_texture || !staging_texture->Map())
+ // Create a temporary staging buffer that is destroyed after the image is copied.
+ temp_buffer = StagingBuffer::Create(STAGING_BUFFER_TYPE_UPLOAD, upload_size,
+ VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
+ if (!temp_buffer || !temp_buffer->Map())
{
PanicAlert("Failed to allocate staging texture for large texture upload.");
return;
}
- // Copy data to staging texture first, then to the "real" texture.
- staging_texture->WriteTexels(0, 0, width, height, buffer, source_pitch);
- staging_texture->CopyToImage(g_command_buffer_mgr->GetCurrentInitCommandBuffer(),
- m_texture->GetImage(), VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, width,
- height, level, 0);
+ upload_buffer = temp_buffer->GetBuffer();
+ upload_buffer_offset = 0;
+ temp_buffer->Write(0, buffer, upload_size, true);
+ temp_buffer->Unmap();
}
+
+ // Copy from the streaming buffer to the actual image.
+ VkBufferImageCopy image_copy = {
+ upload_buffer_offset, // VkDeviceSize bufferOffset
+ row_length, // uint32_t bufferRowLength
+ 0, // uint32_t bufferImageHeight
+ {VK_IMAGE_ASPECT_COLOR_BIT, level, 0, 1}, // VkImageSubresourceLayers imageSubresource
+ {0, 0, 0}, // VkOffset3D imageOffset
+ {width, height, 1} // VkExtent3D imageExtent
+ };
+ vkCmdCopyBufferToImage(g_command_buffer_mgr->GetCurrentInitCommandBuffer(), upload_buffer,
+ m_texture->GetImage(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1,
+ &image_copy);
}
void TextureCache::TCacheEntry::FromRenderTarget(bool is_depth_copy, const EFBRectangle& src_rect,
@@ -544,6 +527,11 @@ bool TextureCache::TCacheEntry::Save(const std::string& filename, unsigned int l
{
_assert_(level < config.levels);
+ // 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_(config.format == HostTextureFormat::RGBA8);
+
// Determine dimensions of image we want to save.
u32 level_width = std::max(1u, config.width >> level);
u32 level_height = std::max(1u, config.height >> level);
@@ -582,7 +570,8 @@ bool TextureCache::TCacheEntry::Save(const std::string& filename, unsigned int l
// 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(staging_texture->GetMapPointer()),
- staging_texture->GetRowStride(), filename, level_width, level_height);
+ static_cast(staging_texture->GetRowStride()), filename,
+ level_width, level_height);
staging_texture->Unmap();
return result;
diff --git a/Source/Core/VideoBackends/Vulkan/TextureCache.h b/Source/Core/VideoBackends/Vulkan/TextureCache.h
index 5fd25c23d8..884d82ddf4 100644
--- a/Source/Core/VideoBackends/Vulkan/TextureCache.h
+++ b/Source/Core/VideoBackends/Vulkan/TextureCache.h
@@ -27,8 +27,8 @@ public:
Texture2D* GetTexture() const { return m_texture.get(); }
VkFramebuffer GetFramebuffer() const { return m_framebuffer; }
- void Load(const u8* buffer, unsigned int width, unsigned int height,
- unsigned int expanded_width, unsigned int level) override;
+ void Load(u32 level, u32 width, u32 height, u32 row_length, const u8* buffer,
+ size_t buffer_size) override;
void FromRenderTarget(bool is_depth_copy, const EFBRectangle& src_rect, bool scale_by_half,
unsigned int cbufid, const float* colmat) override;
void CopyRectangleFromTexture(const TCacheEntryBase* source,
diff --git a/Source/Core/VideoBackends/Vulkan/Util.cpp b/Source/Core/VideoBackends/Vulkan/Util.cpp
index 5de96e23cb..7ce433a79e 100644
--- a/Source/Core/VideoBackends/Vulkan/Util.cpp
+++ b/Source/Core/VideoBackends/Vulkan/Util.cpp
@@ -53,6 +53,20 @@ bool IsDepthFormat(VkFormat format)
}
}
+bool IsCompressedFormat(VkFormat format)
+{
+ switch (format)
+ {
+ case VK_FORMAT_BC1_RGBA_UNORM_BLOCK:
+ case VK_FORMAT_BC2_UNORM_BLOCK:
+ case VK_FORMAT_BC3_UNORM_BLOCK:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
VkFormat GetLinearFormat(VkFormat format)
{
switch (format)
@@ -74,6 +88,25 @@ VkFormat GetLinearFormat(VkFormat format)
}
}
+VkFormat GetVkFormatForHostTextureFormat(HostTextureFormat format)
+{
+ switch (format)
+ {
+ case HostTextureFormat::DXT1:
+ return VK_FORMAT_BC1_RGBA_UNORM_BLOCK;
+
+ case HostTextureFormat::DXT3:
+ return VK_FORMAT_BC2_UNORM_BLOCK;
+
+ case HostTextureFormat::DXT5:
+ return VK_FORMAT_BC3_UNORM_BLOCK;
+
+ case HostTextureFormat::RGBA8:
+ default:
+ return VK_FORMAT_R8G8B8A8_UNORM;
+ }
+}
+
u32 GetTexelSize(VkFormat format)
{
// Only contains pixel formats we use.
@@ -91,12 +124,33 @@ u32 GetTexelSize(VkFormat format)
case VK_FORMAT_B8G8R8A8_UNORM:
return 4;
+ case VK_FORMAT_BC1_RGBA_UNORM_BLOCK:
+ return 8;
+
+ case VK_FORMAT_BC2_UNORM_BLOCK:
+ case VK_FORMAT_BC3_UNORM_BLOCK:
+ return 16;
+
default:
PanicAlert("Unhandled pixel format");
return 1;
}
}
+u32 GetBlockSize(VkFormat format)
+{
+ switch (format)
+ {
+ case VK_FORMAT_BC1_RGBA_UNORM_BLOCK:
+ case VK_FORMAT_BC2_UNORM_BLOCK:
+ case VK_FORMAT_BC3_UNORM_BLOCK:
+ return 4;
+
+ default:
+ return 1;
+ }
+}
+
VkRect2D ClampRect2D(const VkRect2D& rect, u32 width, u32 height)
{
VkRect2D out;
diff --git a/Source/Core/VideoBackends/Vulkan/Util.h b/Source/Core/VideoBackends/Vulkan/Util.h
index d61c699bfe..85d447cf75 100644
--- a/Source/Core/VideoBackends/Vulkan/Util.h
+++ b/Source/Core/VideoBackends/Vulkan/Util.h
@@ -25,8 +25,11 @@ size_t AlignBufferOffset(size_t offset, size_t alignment);
u32 MakeRGBA8Color(float r, float g, float b, float a);
bool IsDepthFormat(VkFormat format);
+bool IsCompressedFormat(VkFormat format);
VkFormat GetLinearFormat(VkFormat format);
+VkFormat GetVkFormatForHostTextureFormat(HostTextureFormat format);
u32 GetTexelSize(VkFormat format);
+u32 GetBlockSize(VkFormat format);
// Clamps a VkRect2D to the specified dimensions.
VkRect2D ClampRect2D(const VkRect2D& rect, u32 width, u32 height);
diff --git a/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp b/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp
index 58b10dd92e..8978cd2df6 100644
--- a/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp
+++ b/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp
@@ -245,6 +245,7 @@ void VulkanContext::PopulateBackendInfo(VideoConfig* config)
config->backend_info.bSupportsFragmentStoresAndAtomics = false; // Dependent on features.
config->backend_info.bSupportsSSAA = false; // Dependent on features.
config->backend_info.bSupportsDepthClamp = false; // Dependent on features.
+ config->backend_info.bSupportsST3CTextures = false; // Dependent on features.
config->backend_info.bSupportsReversedDepthRange = false; // No support yet due to driver bugs.
}
@@ -283,6 +284,9 @@ void VulkanContext::PopulateBackendInfoFeatures(VideoConfig* config, VkPhysicalD
config->backend_info.bSupportsDepthClamp =
(features.depthClamp == VK_TRUE && features.shaderClipDistance == VK_TRUE);
+ // textureCompressionBC implies BC1 through BC7, which is a superset of DXT1/3/5, which we need.
+ config->backend_info.bSupportsST3CTextures = features.textureCompressionBC == VK_TRUE;
+
// Our usage of primitive restart appears to be broken on AMD's binary drivers.
// Seems to be fine on GCN Gen 1-2, unconfirmed on GCN Gen 3, causes driver resets on GCN Gen 4.
if (DriverDetails::HasBug(DriverDetails::BUG_PRIMITIVE_RESTART))
@@ -459,6 +463,7 @@ bool VulkanContext::SelectDeviceFeatures()
m_device_features.occlusionQueryPrecise = available_features.occlusionQueryPrecise;
m_device_features.shaderClipDistance = available_features.shaderClipDistance;
m_device_features.depthClamp = available_features.depthClamp;
+ m_device_features.textureCompressionBC = available_features.textureCompressionBC;
return true;
}
diff --git a/Source/Core/VideoCommon/CMakeLists.txt b/Source/Core/VideoCommon/CMakeLists.txt
index 7651798e05..7eef4f715d 100644
--- a/Source/Core/VideoCommon/CMakeLists.txt
+++ b/Source/Core/VideoCommon/CMakeLists.txt
@@ -14,6 +14,7 @@ set(SRCS
GeometryShaderGen.cpp
GeometryShaderManager.cpp
HiresTextures.cpp
+ HiresTextures_DDSLoader.cpp
ImageWrite.cpp
IndexGenerator.cpp
LightingShaderGen.cpp
diff --git a/Source/Core/VideoCommon/HiresTextures.cpp b/Source/Core/VideoCommon/HiresTextures.cpp
index f4ad7448a5..d601370fb5 100644
--- a/Source/Core/VideoCommon/HiresTextures.cpp
+++ b/Source/Core/VideoCommon/HiresTextures.cpp
@@ -366,6 +366,21 @@ std::string HiresTexture::GenBaseName(const u8* texture, size_t texture_size, co
return name;
}
+u32 HiresTexture::CalculateMipCount(u32 width, u32 height)
+{
+ u32 mip_width = width;
+ u32 mip_height = height;
+ u32 mip_count = 1;
+ while (mip_width > 1 || mip_height > 1)
+ {
+ mip_width = std::max(mip_width / 2, 1u);
+ mip_height = std::max(mip_height / 2, 1u);
+ mip_count++;
+ }
+
+ return mip_count;
+}
+
std::shared_ptr HiresTexture::Search(const u8* texture, size_t texture_size,
const u8* tlut, size_t tlut_size, u32 width,
u32 height, int format, bool has_mipmaps)
@@ -395,81 +410,134 @@ std::shared_ptr HiresTexture::Search(const u8* texture, size_t tex
std::unique_ptr HiresTexture::Load(const std::string& base_filename, u32 width,
u32 height)
{
- std::unique_ptr ret;
- for (int level = 0;; level++)
+ // We need to have a level 0 custom texture to even consider loading.
+ auto filename_iter = s_textureMap.find(base_filename);
+ if (filename_iter == s_textureMap.end())
+ return nullptr;
+
+ // Try to load level 0 (and any mipmaps) from a DDS file.
+ // If this fails, it's fine, we'll just load level0 again using SOIL.
+ // Can't use make_unique due to private constructor.
+ std::unique_ptr ret = std::unique_ptr(new HiresTexture());
+ const std::string& first_mip_filename = filename_iter->second;
+ LoadDDSTexture(ret.get(), first_mip_filename);
+
+ // Load remaining mip levels, or from the start if it's not a DDS texture.
+ for (u32 mip_level = static_cast(ret->m_levels.size());; mip_level++)
{
std::string filename = base_filename;
- if (level)
- {
- filename += StringFromFormat("_mip%u", level);
- }
+ if (mip_level != 0)
+ filename += StringFromFormat("_mip%u", mip_level);
- if (s_textureMap.find(filename) != s_textureMap.end())
- {
- Level l;
+ filename_iter = s_textureMap.find(filename);
+ if (filename_iter == s_textureMap.end())
+ break;
+ // Try loading DDS textures first, that way we maintain compression of DXT formats.
+ // TODO: Reduce the number of open() calls here. We could use one fd.
+ Level level;
+ if (!LoadDDSTexture(level, filename_iter->second))
+ {
File::IOFile file;
- file.Open(s_textureMap[filename], "rb");
+ file.Open(filename_iter->second, "rb");
std::vector buffer(file.GetSize());
file.ReadBytes(buffer.data(), file.GetSize());
-
- int channels;
- l.data =
- SOILPointer(SOIL_load_image_from_memory(buffer.data(), (int)buffer.size(), (int*)&l.width,
- (int*)&l.height, &channels, SOIL_LOAD_RGBA),
- SOIL_free_image_data);
- l.data_size = (size_t)l.width * l.height * 4;
-
- if (l.data == nullptr)
+ if (!LoadTexture(level, buffer))
{
ERROR_LOG(VIDEO, "Custom texture %s failed to load", filename.c_str());
break;
}
+ }
- if (!level)
- {
- if (l.width * height != l.height * width)
- ERROR_LOG(VIDEO, "Invalid custom texture size %dx%d for texture %s. The aspect differs "
- "from the native size %dx%d.",
- l.width, l.height, filename.c_str(), width, height);
- if (width && height && (l.width % width || l.height % height))
- WARN_LOG(VIDEO, "Invalid custom texture size %dx%d for texture %s. Please use an integer "
- "upscaling factor based on the native size %dx%d.",
- l.width, l.height, filename.c_str(), width, height);
- width = l.width;
- height = l.height;
- }
- else if (width != l.width || height != l.height)
- {
- ERROR_LOG(
- VIDEO,
- "Invalid custom texture size %dx%d for texture %s. This mipmap layer _must_ be %dx%d.",
- l.width, l.height, filename.c_str(), width, height);
- l.data.reset();
- break;
- }
+ ret->m_levels.push_back(std::move(level));
+ }
- if (!ret)
- ret = std::unique_ptr(new HiresTexture);
- ret->m_levels.push_back(std::move(l));
+ // If we failed to load any mip levels, we can't use this texture at all.
+ if (ret->m_levels.empty())
+ return nullptr;
- // no more mipmaps available
- if (width == 1 && height == 1)
- break;
+ // Verify that the aspect ratio of the texture hasn't changed, as this could have side-effects.
+ const Level& first_mip = ret->m_levels[0];
+ if (first_mip.width * height != first_mip.height * width)
+ {
+ ERROR_LOG(VIDEO, "Invalid custom texture size %ux%u for texture %s. The aspect differs "
+ "from the native size %ux%u.",
+ first_mip.width, first_mip.height, first_mip_filename.c_str(), width, height);
+ }
- // calculate the size of the next mipmap
- width = std::max(1u, width >> 1);
- height = std::max(1u, height >> 1);
+ // Same deal if the custom texture isn't a multiple of the native size.
+ if (width != 0 && height != 0 && (first_mip.width % width || first_mip.height % height))
+ {
+ ERROR_LOG(VIDEO, "Invalid custom texture size %ux%u for texture %s. Please use an integer "
+ "upscaling factor based on the native size %ux%u.",
+ first_mip.width, first_mip.height, first_mip_filename.c_str(), width, height);
+ }
+
+ // Verify that each mip level is the correct size (divide by 2 each time).
+ u32 current_mip_width = first_mip.width;
+ u32 current_mip_height = first_mip.height;
+ for (u32 mip_level = 1; mip_level < static_cast(ret->m_levels.size()); mip_level++)
+ {
+ if (current_mip_width != 1 || current_mip_height != 1)
+ {
+ current_mip_width = std::max(current_mip_width / 2, 1u);
+ current_mip_height = std::max(current_mip_height / 2, 1u);
+
+ const Level& level = ret->m_levels[mip_level];
+ if (current_mip_width == level.width && current_mip_height == level.height)
+ continue;
+
+ ERROR_LOG(VIDEO,
+ "Invalid custom texture size %dx%d for texture %s. Mipmap level %u must be %dx%d.",
+ level.width, level.height, first_mip_filename.c_str(), mip_level, current_mip_width,
+ current_mip_height);
}
else
{
- break;
+ // It is invalid to have more than a single 1x1 mipmap.
+ ERROR_LOG(VIDEO, "Custom texture %s has too many 1x1 mipmaps. Skipping extra levels.",
+ first_mip_filename.c_str());
}
+
+ // Drop this mip level and any others after it.
+ while (ret->m_levels.size() > mip_level)
+ ret->m_levels.pop_back();
+ }
+
+ // All levels have to have the same format.
+ if (std::any_of(ret->m_levels.begin(), ret->m_levels.end(),
+ [&ret](const Level& l) { return l.format != ret->m_levels[0].format; }))
+ {
+ ERROR_LOG(VIDEO, "Custom texture %s has inconsistent formats across mip levels.",
+ first_mip_filename.c_str());
+
+ return nullptr;
}
return ret;
}
+bool HiresTexture::LoadTexture(Level& level, const std::vector& buffer)
+{
+ int channels;
+ int width;
+ int height;
+
+ u8* data = SOIL_load_image_from_memory(buffer.data(), static_cast(buffer.size()), &width,
+ &height, &channels, SOIL_LOAD_RGBA);
+ if (!data)
+ return false;
+
+ // Images loaded by SOIL are converted to RGBA.
+ level.width = static_cast(width);
+ level.height = static_cast(height);
+ level.format = HostTextureFormat::RGBA8;
+ level.data = ImageDataPointer(data, SOIL_free_image_data);
+ level.row_length = level.width;
+ level.data_size = static_cast(level.row_length) * 4 * level.height;
+ return true;
+}
+
std::string HiresTexture::GetTextureDirectory(const std::string& game_id)
{
const std::string texture_directory = File::GetUserPath(D_HIRESTEXTURES_IDX) + game_id;
@@ -484,3 +552,8 @@ std::string HiresTexture::GetTextureDirectory(const std::string& game_id)
HiresTexture::~HiresTexture()
{
}
+
+HostTextureFormat HiresTexture::GetFormat() const
+{
+ return m_levels.at(0).format;
+}
diff --git a/Source/Core/VideoCommon/HiresTextures.h b/Source/Core/VideoCommon/HiresTextures.h
index b9ddc3d793..30bb96d40c 100644
--- a/Source/Core/VideoCommon/HiresTextures.h
+++ b/Source/Core/VideoCommon/HiresTextures.h
@@ -9,11 +9,12 @@
#include
#include "Common/CommonTypes.h"
+#include "VideoCommon/VideoCommon.h"
class HiresTexture
{
public:
- using SOILPointer = std::unique_ptr;
+ using ImageDataPointer = std::unique_ptr;
static void Init();
static void Update();
@@ -27,22 +28,30 @@ public:
size_t tlut_size, u32 width, u32 height, int format,
bool has_mipmaps, bool dump = false);
+ static u32 CalculateMipCount(u32 width, u32 height);
+
~HiresTexture();
+ HostTextureFormat GetFormat() const;
struct Level
{
Level();
- SOILPointer data;
- size_t data_size = 0;
+ ImageDataPointer data;
+ HostTextureFormat format = HostTextureFormat::RGBA8;
u32 width = 0;
u32 height = 0;
+ u32 row_length = 0;
+ size_t data_size = 0;
};
std::vector m_levels;
private:
static std::unique_ptr Load(const std::string& base_filename, u32 width,
u32 height);
+ static bool LoadDDSTexture(HiresTexture* tex, const std::string& filename);
+ static bool LoadDDSTexture(Level& level, const std::string& filename);
+ static bool LoadTexture(Level& level, const std::vector& buffer);
static void Prefetch();
static std::string GetTextureDirectory(const std::string& game_id);
diff --git a/Source/Core/VideoCommon/HiresTextures_DDSLoader.cpp b/Source/Core/VideoCommon/HiresTextures_DDSLoader.cpp
new file mode 100644
index 0000000000..d58d699a56
--- /dev/null
+++ b/Source/Core/VideoCommon/HiresTextures_DDSLoader.cpp
@@ -0,0 +1,486 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include "VideoCommon/HiresTextures.h"
+#include
+#include
+#include
+#include
+#include
+#include "Common/Align.h"
+#include "Common/FileUtil.h"
+#include "Common/Swap.h"
+#include "VideoCommon/VideoConfig.h"
+
+namespace
+{
+// From https://raw.githubusercontent.com/Microsoft/DirectXTex/master/DirectXTex/DDS.h
+//
+// This header defines constants and structures that are useful when parsing
+// DDS files. DDS files were originally designed to use several structures
+// and constants that are native to DirectDraw and are defined in ddraw.h,
+// such as DDSURFACEDESC2 and DDSCAPS2. This file defines similar
+// (compatible) constants and structures so that one can use DDS files
+// without needing to include ddraw.h.
+//
+// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
+// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+// http://go.microsoft.com/fwlink/?LinkId=248926
+
+#pragma pack(push, 1)
+
+const uint32_t DDS_MAGIC = 0x20534444; // "DDS "
+
+struct DDS_PIXELFORMAT
+{
+ uint32_t dwSize;
+ uint32_t dwFlags;
+ uint32_t dwFourCC;
+ uint32_t dwRGBBitCount;
+ uint32_t dwRBitMask;
+ uint32_t dwGBitMask;
+ uint32_t dwBBitMask;
+ uint32_t dwABitMask;
+};
+
+#define DDS_FOURCC 0x00000004 // DDPF_FOURCC
+#define DDS_RGB 0x00000040 // DDPF_RGB
+#define DDS_RGBA 0x00000041 // DDPF_RGB | DDPF_ALPHAPIXELS
+#define DDS_LUMINANCE 0x00020000 // DDPF_LUMINANCE
+#define DDS_LUMINANCEA 0x00020001 // DDPF_LUMINANCE | DDPF_ALPHAPIXELS
+#define DDS_ALPHA 0x00000002 // DDPF_ALPHA
+#define DDS_PAL8 0x00000020 // DDPF_PALETTEINDEXED8
+#define DDS_PAL8A 0x00000021 // DDPF_PALETTEINDEXED8 | DDPF_ALPHAPIXELS
+#define DDS_BUMPDUDV 0x00080000 // DDPF_BUMPDUDV
+
+#ifndef MAKEFOURCC
+#define MAKEFOURCC(ch0, ch1, ch2, ch3) \
+ ((uint32_t)(uint8_t)(ch0) | ((uint32_t)(uint8_t)(ch1) << 8) | ((uint32_t)(uint8_t)(ch2) << 16) | \
+ ((uint32_t)(uint8_t)(ch3) << 24))
+#endif /* defined(MAKEFOURCC) */
+
+#define DDS_HEADER_FLAGS_TEXTURE \
+ 0x00001007 // DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT
+#define DDS_HEADER_FLAGS_MIPMAP 0x00020000 // DDSD_MIPMAPCOUNT
+#define DDS_HEADER_FLAGS_VOLUME 0x00800000 // DDSD_DEPTH
+#define DDS_HEADER_FLAGS_PITCH 0x00000008 // DDSD_PITCH
+#define DDS_HEADER_FLAGS_LINEARSIZE 0x00080000 // DDSD_LINEARSIZE
+
+// Subset here matches D3D10_RESOURCE_DIMENSION and D3D11_RESOURCE_DIMENSION
+enum DDS_RESOURCE_DIMENSION
+{
+ DDS_DIMENSION_TEXTURE1D = 2,
+ DDS_DIMENSION_TEXTURE2D = 3,
+ DDS_DIMENSION_TEXTURE3D = 4,
+};
+
+struct DDS_HEADER
+{
+ uint32_t dwSize;
+ uint32_t dwFlags;
+ uint32_t dwHeight;
+ uint32_t dwWidth;
+ uint32_t dwPitchOrLinearSize;
+ uint32_t dwDepth; // only if DDS_HEADER_FLAGS_VOLUME is set in dwFlags
+ uint32_t dwMipMapCount;
+ uint32_t dwReserved1[11];
+ DDS_PIXELFORMAT ddspf;
+ uint32_t dwCaps;
+ uint32_t dwCaps2;
+ uint32_t dwCaps3;
+ uint32_t dwCaps4;
+ uint32_t dwReserved2;
+};
+
+struct DDS_HEADER_DXT10
+{
+ uint32_t dxgiFormat;
+ uint32_t resourceDimension;
+ uint32_t miscFlag; // see DDS_RESOURCE_MISC_FLAG
+ uint32_t arraySize;
+ uint32_t miscFlags2; // see DDS_MISC_FLAGS2
+};
+
+#pragma pack(pop)
+
+static_assert(sizeof(DDS_HEADER) == 124, "DDS Header size mismatch");
+static_assert(sizeof(DDS_HEADER_DXT10) == 20, "DDS DX10 Extended Header size mismatch");
+
+constexpr DDS_PIXELFORMAT DDSPF_A8R8G8B8 = {
+ sizeof(DDS_PIXELFORMAT), DDS_RGBA, 0, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000};
+constexpr DDS_PIXELFORMAT DDSPF_X8R8G8B8 = {
+ sizeof(DDS_PIXELFORMAT), DDS_RGB, 0, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000};
+constexpr DDS_PIXELFORMAT DDSPF_A8B8G8R8 = {
+ sizeof(DDS_PIXELFORMAT), DDS_RGBA, 0, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000};
+constexpr DDS_PIXELFORMAT DDSPF_X8B8G8R8 = {
+ sizeof(DDS_PIXELFORMAT), DDS_RGB, 0, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0x00000000};
+constexpr DDS_PIXELFORMAT DDSPF_R8G8B8 = {
+ sizeof(DDS_PIXELFORMAT), DDS_RGB, 0, 24, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000};
+
+// End of Microsoft code from DDS.h.
+
+bool DDSPixelFormatMatches(const DDS_PIXELFORMAT& pf1, const DDS_PIXELFORMAT& pf2)
+{
+ return std::tie(pf1.dwSize, pf1.dwFlags, pf1.dwFourCC, pf1.dwRGBBitCount, pf1.dwRBitMask,
+ pf1.dwGBitMask, pf1.dwGBitMask, pf1.dwBBitMask, pf1.dwABitMask) ==
+ std::tie(pf2.dwSize, pf2.dwFlags, pf2.dwFourCC, pf2.dwRGBBitCount, pf2.dwRBitMask,
+ pf2.dwGBitMask, pf2.dwGBitMask, pf2.dwBBitMask, pf2.dwABitMask);
+}
+
+struct DDSLoadInfo
+{
+ u32 block_size = 1;
+ u32 bytes_per_block = 4;
+ u32 width = 0;
+ u32 height = 0;
+ u32 mip_count = 0;
+ HostTextureFormat format = HostTextureFormat::RGBA8;
+ size_t first_mip_offset = 0;
+ size_t first_mip_size = 0;
+ u32 first_mip_row_length = 0;
+
+ std::function conversion_function;
+};
+
+u32 GetBlockCount(u32 extent, u32 block_size)
+{
+ return std::max(Common::AlignUp(extent, block_size) / block_size, 1u);
+}
+
+HiresTexture::ImageDataPointer AllocateLevelData(size_t size)
+{
+ return HiresTexture::ImageDataPointer(new u8[size], [](u8* data) { delete[] data; });
+}
+
+void ConvertTexture_X8B8G8R8(HiresTexture::Level* level)
+{
+ u8* data_ptr = level->data.get();
+ for (u32 row = 0; row < level->height; row++)
+ {
+ for (u32 x = 0; x < level->row_length; x++)
+ {
+ // Set alpha channel to full intensity.
+ data_ptr[3] = 0xFF;
+ data_ptr += sizeof(u32);
+ }
+ }
+}
+
+void ConvertTexture_A8R8G8B8(HiresTexture::Level* level)
+{
+ u8* data_ptr = level->data.get();
+ for (u32 row = 0; row < level->height; row++)
+ {
+ for (u32 x = 0; x < level->row_length; x++)
+ {
+ // Byte swap ABGR -> RGBA
+ u32 val;
+ std::memcpy(&val, data_ptr, sizeof(val));
+ val = ((val & 0xFF00FF00) | ((val >> 16) & 0xFF) | ((val << 16) & 0xFF0000));
+ std::memcpy(data_ptr, &val, sizeof(u32));
+ data_ptr += sizeof(u32);
+ }
+ }
+}
+
+void ConvertTexture_X8R8G8B8(HiresTexture::Level* level)
+{
+ u8* data_ptr = level->data.get();
+ for (u32 row = 0; row < level->height; row++)
+ {
+ for (u32 x = 0; x < level->row_length; x++)
+ {
+ // Byte swap XBGR -> RGBX, and set alpha to full intensity.
+ u32 val;
+ std::memcpy(&val, data_ptr, sizeof(val));
+ val = ((val & 0x0000FF00) | ((val >> 16) & 0xFF) | ((val << 16) & 0xFF0000)) | 0xFF000000;
+ std::memcpy(data_ptr, &val, sizeof(u32));
+ data_ptr += sizeof(u32);
+ }
+ }
+}
+
+void ConvertTexture_R8G8B8(HiresTexture::Level* level)
+{
+ // Have to reallocate the buffer for this one, since the data in the file
+ // does not have an alpha byte.
+ level->data_size = level->row_length * level->height * sizeof(u32);
+ HiresTexture::ImageDataPointer rgb_data = AllocateLevelData(level->data_size);
+ std::swap(level->data, rgb_data);
+
+ const u8* rgb_data_ptr = rgb_data.get();
+ u8* data_ptr = level->data.get();
+
+ for (u32 row = 0; row < level->height; row++)
+ {
+ for (u32 x = 0; x < level->row_length; x++)
+ {
+ // This is BGR in memory.
+ u32 val;
+ std::memcpy(&val, rgb_data_ptr, sizeof(val));
+ val = ((val & 0x0000FF00) | ((val >> 16) & 0xFF) | ((val << 16) & 0xFF0000)) | 0xFF000000;
+ std::memcpy(data_ptr, &val, sizeof(u32));
+ data_ptr += sizeof(u32);
+ rgb_data_ptr += 3;
+ }
+ }
+}
+
+bool ParseDDSHeader(File::IOFile& file, DDSLoadInfo* info)
+{
+ // Exit as early as possible for non-DDS textures, since all extensions are currently
+ // passed through this function.
+ u32 magic;
+ if (!file.ReadBytes(&magic, sizeof(magic)) || magic != DDS_MAGIC)
+ return false;
+
+ DDS_HEADER header;
+ size_t header_size = sizeof(header);
+ if (!file.ReadBytes(&header, header_size) || header.dwSize < header_size)
+ return false;
+
+ // Required fields.
+ if ((header.dwFlags & DDS_HEADER_FLAGS_TEXTURE) != DDS_HEADER_FLAGS_TEXTURE)
+ return false;
+
+ // Image should be 2D.
+ if (header.dwFlags & DDS_HEADER_FLAGS_VOLUME)
+ return false;
+
+ // Presence of width/height fields is already tested by DDS_HEADER_FLAGS_TEXTURE.
+ info->width = header.dwWidth;
+ info->height = header.dwHeight;
+ if (info->width == 0 || info->height == 0)
+ return false;
+
+ // Check for mip levels.
+ if (header.dwFlags & DDS_HEADER_FLAGS_MIPMAP)
+ {
+ info->mip_count = header.dwMipMapCount;
+ if (header.dwMipMapCount != 0)
+ info->mip_count = header.dwMipMapCount;
+ else
+ info->mip_count = HiresTexture::CalculateMipCount(info->width, info->height);
+ }
+ else
+ {
+ info->mip_count = 1;
+ }
+
+ // Handle fourcc formats vs uncompressed formats.
+ bool has_fourcc = (header.ddspf.dwFlags & DDS_FOURCC) != 0;
+ bool needs_s3tc = false;
+ if (has_fourcc)
+ {
+ // Handle DX10 extension header.
+ u32 dxt10_format = 0;
+ if (header.ddspf.dwFourCC == MAKEFOURCC('D', 'X', '1', '0'))
+ {
+ DDS_HEADER_DXT10 dxt10_header;
+ if (!file.ReadBytes(&dxt10_header, sizeof(dxt10_header)))
+ return false;
+
+ // Can't handle array textures here. Doesn't make sense to use them, anyway.
+ if (dxt10_header.resourceDimension != DDS_DIMENSION_TEXTURE2D || dxt10_header.arraySize != 1)
+ return false;
+
+ header_size += sizeof(dxt10_header);
+ dxt10_format = dxt10_header.dxgiFormat;
+ }
+
+ // Currently, we only handle compressed textures here, and leave the rest to the SOIL loader.
+ // In the future, this could be extended, but these isn't much benefit in doing so currently.
+ if (header.ddspf.dwFourCC == MAKEFOURCC('D', 'X', 'T', '1') || dxt10_format == 71)
+ {
+ info->format = HostTextureFormat::DXT1;
+ info->block_size = 4;
+ info->bytes_per_block = 8;
+ needs_s3tc = true;
+ }
+ else if (header.ddspf.dwFourCC == MAKEFOURCC('D', 'X', 'T', '3') || dxt10_format == 74)
+ {
+ info->format = HostTextureFormat::DXT3;
+ info->block_size = 4;
+ info->bytes_per_block = 16;
+ needs_s3tc = true;
+ }
+ else if (header.ddspf.dwFourCC == MAKEFOURCC('D', 'X', 'T', '5') || dxt10_format == 77)
+ {
+ info->format = HostTextureFormat::DXT5;
+ info->block_size = 4;
+ info->bytes_per_block = 16;
+ needs_s3tc = true;
+ }
+ else
+ {
+ // Leave all remaining formats to SOIL.
+ return false;
+ }
+ }
+ else
+ {
+ if (DDSPixelFormatMatches(header.ddspf, DDSPF_A8R8G8B8))
+ {
+ info->conversion_function = ConvertTexture_A8R8G8B8;
+ }
+ else if (DDSPixelFormatMatches(header.ddspf, DDSPF_X8R8G8B8))
+ {
+ info->conversion_function = ConvertTexture_X8R8G8B8;
+ }
+ else if (DDSPixelFormatMatches(header.ddspf, DDSPF_X8B8G8R8))
+ {
+ info->conversion_function = ConvertTexture_X8B8G8R8;
+ }
+ else if (DDSPixelFormatMatches(header.ddspf, DDSPF_R8G8B8))
+ {
+ info->conversion_function = ConvertTexture_R8G8B8;
+ }
+ else if (DDSPixelFormatMatches(header.ddspf, DDSPF_A8B8G8R8))
+ {
+ // This format is already in RGBA order, so no conversion necessary.
+ }
+ else
+ {
+ return false;
+ }
+
+ // All these formats are RGBA, just with byte swapping.
+ info->format = HostTextureFormat::RGBA8;
+ info->block_size = 1;
+ info->bytes_per_block = header.ddspf.dwRGBBitCount / 8;
+ }
+
+ // We also need to ensure the backend supports these formats natively before loading them,
+ // otherwise, fallback to SOIL, which will decompress them to RGBA.
+ if (needs_s3tc && !g_ActiveConfig.backend_info.bSupportsST3CTextures)
+ return false;
+
+ // Mip levels smaller than the block size are padded to multiples of the block size.
+ u32 blocks_wide = GetBlockCount(info->width, info->block_size);
+ u32 blocks_high = GetBlockCount(info->height, info->block_size);
+
+ // Pitch can be specified in the header, otherwise we can derive it from the dimensions. For
+ // compressed formats, both DDS_HEADER_FLAGS_LINEARSIZE and DDS_HEADER_FLAGS_PITCH should be
+ // set. See https://msdn.microsoft.com/en-us/library/windows/desktop/bb943982(v=vs.85).aspx
+ if (header.dwFlags & DDS_HEADER_FLAGS_PITCH && header.dwFlags & DDS_HEADER_FLAGS_LINEARSIZE)
+ {
+ // Convert pitch (in bytes) to texels/row length.
+ if (header.dwPitchOrLinearSize < info->bytes_per_block)
+ {
+ // Likely a corrupted or invalid file.
+ return false;
+ }
+
+ info->first_mip_row_length =
+ std::max(header.dwPitchOrLinearSize / info->bytes_per_block, 1u) * info->block_size;
+ info->first_mip_size = static_cast(info->first_mip_row_length / info->block_size) *
+ info->block_size * blocks_high;
+ }
+ else
+ {
+ // Assume no padding between rows of blocks.
+ info->first_mip_row_length = blocks_wide * info->block_size;
+ info->first_mip_size = blocks_wide * static_cast(info->bytes_per_block) * blocks_high;
+ }
+
+ // Check for truncated or corrupted files.
+ info->first_mip_offset = sizeof(magic) + header_size;
+ if (info->first_mip_offset >= file.GetSize())
+ return false;
+
+ return true;
+}
+
+bool ReadMipLevel(HiresTexture::Level* level, File::IOFile& file, const DDSLoadInfo& info,
+ u32 width, u32 height, u32 row_length, size_t size)
+{
+ // Copy to the final storage location. The deallocator here is simple, nothing extra is
+ // needed, compared to the SOIL-based loader.
+ level->width = width;
+ level->height = height;
+ level->format = info.format;
+ level->row_length = row_length;
+ level->data_size = size;
+ level->data = AllocateLevelData(level->data_size);
+ if (!file.ReadBytes(level->data.get(), level->data_size))
+ {
+ level->data.reset();
+ return false;
+ }
+
+ // Apply conversion function for uncompressed textures.
+ if (info.conversion_function)
+ info.conversion_function(level);
+
+ return true;
+}
+
+} // namespace
+
+bool HiresTexture::LoadDDSTexture(HiresTexture* tex, const std::string& filename)
+{
+ File::IOFile file;
+ file.Open(filename, "rb");
+ if (!file.IsOpen())
+ return false;
+
+ DDSLoadInfo info;
+ if (!ParseDDSHeader(file, &info))
+ return false;
+
+ // Read first mip level, as it may have a custom pitch.
+ Level first_level;
+ if (!file.Seek(info.first_mip_offset, SEEK_SET) ||
+ !ReadMipLevel(&first_level, file, info, info.width, info.height, info.first_mip_row_length,
+ info.first_mip_size))
+ {
+ return false;
+ }
+
+ tex->m_levels.push_back(std::move(first_level));
+
+ // Read in any remaining mip levels in the file.
+ // If the .dds file does not contain a full mip chain, we'll fall back to the old path.
+ u32 mip_width = info.width;
+ u32 mip_height = info.height;
+ for (u32 i = 1; i < info.mip_count; i++)
+ {
+ mip_width = std::max(mip_width / 2, 1u);
+ mip_height = std::max(mip_height / 2, 1u);
+
+ // Pitch can't be specified with each mip level, so we have to calculate it ourselves.
+ u32 blocks_wide = GetBlockCount(mip_width, info.block_size);
+ u32 blocks_high = GetBlockCount(mip_height, info.block_size);
+ u32 mip_row_length = blocks_wide * info.block_size;
+ size_t mip_size = blocks_wide * static_cast(info.bytes_per_block) * blocks_high;
+ Level level;
+ if (!ReadMipLevel(&level, file, info, mip_width, mip_height, mip_row_length, mip_size))
+ break;
+
+ tex->m_levels.push_back(std::move(level));
+ }
+
+ return true;
+}
+
+bool HiresTexture::LoadDDSTexture(Level& level, const std::string& filename)
+{
+ // Only loading a single mip level.
+ File::IOFile file;
+ file.Open(filename, "rb");
+ if (!file.IsOpen())
+ return false;
+
+ DDSLoadInfo info;
+ if (!ParseDDSHeader(file, &info))
+ return false;
+
+ return ReadMipLevel(&level, file, info, info.width, info.height, info.first_mip_row_length,
+ info.first_mip_size);
+}
diff --git a/Source/Core/VideoCommon/TextureCacheBase.cpp b/Source/Core/VideoCommon/TextureCacheBase.cpp
index 89d473cfec..613f2dfed9 100644
--- a/Source/Core/VideoCommon/TextureCacheBase.cpp
+++ b/Source/Core/VideoCommon/TextureCacheBase.cpp
@@ -47,6 +47,29 @@ TextureCacheBase::TCacheEntryBase::~TCacheEntryBase()
{
}
+bool TextureCacheBase::IsCompressedHostTextureFormat(HostTextureFormat format)
+{
+ // This will need to be changed if we add any other uncompressed formats.
+ return format != HostTextureFormat::RGBA8;
+}
+
+size_t TextureCacheBase::CalculateHostTextureLevelPitch(HostTextureFormat format, u32 row_length)
+{
+ switch (format)
+ {
+ case HostTextureFormat::DXT1:
+ return static_cast(std::max(1u, row_length / 4)) * 8;
+
+ case HostTextureFormat::DXT3:
+ case HostTextureFormat::DXT5:
+ return static_cast(std::max(1u, row_length / 4)) * 16;
+
+ case HostTextureFormat::RGBA8:
+ default:
+ return static_cast(row_length) * 4;
+ }
+}
+
void TextureCacheBase::CheckTempSize(size_t required_size)
{
if (required_size <= temp_size)
@@ -750,8 +773,6 @@ TextureCacheBase::TCacheEntryBase* TextureCacheBase::Load(const u32 stage)
}
expandedWidth = level.width;
expandedHeight = level.height;
- CheckTempSize(level.data_size);
- memcpy(temp, level.data.get(), level.data_size);
}
}
@@ -774,6 +795,7 @@ TextureCacheBase::TCacheEntryBase* TextureCacheBase::Load(const u32 stage)
config.width = width;
config.height = height;
config.levels = texLevels;
+ config.format = hires_tex ? hires_tex->GetFormat() : HostTextureFormat::RGBA8;
TCacheEntryBase* entry = AllocateTexture(config);
GFX_DEBUGGER_PAUSE_AT(NEXT_NEW_TEXTURE, true);
@@ -784,17 +806,20 @@ TextureCacheBase::TCacheEntryBase* TextureCacheBase::Load(const u32 stage)
const u8* tlut = &texMem[tlutaddr];
if (hires_tex)
{
- entry->Load(temp, width, height, expandedWidth, 0);
+ const auto& level = hires_tex->m_levels[0];
+ entry->Load(0, level.width, level.height, level.row_length, level.data.get(), level.data_size);
}
- else if (decode_on_gpu)
+ if (!hires_tex && decode_on_gpu)
{
u32 row_stride = bytes_per_block * (expandedWidth / bsw);
g_texture_cache->DecodeTextureOnGPU(
entry, 0, src_data, texture_size, static_cast(texformat), width, height,
expandedWidth, expandedHeight, row_stride, tlut, static_cast(tlutfmt));
}
- else
+ else if (!hires_tex)
{
+ size_t decoded_texture_size = expandedWidth * sizeof(u32) * expandedHeight;
+ CheckTempSize(decoded_texture_size);
if (!(texformat == GX_TF_RGBA8 && from_tmem))
{
TexDecoder_Decode(temp, src_data, expandedWidth, expandedHeight, texformat, tlut,
@@ -807,7 +832,7 @@ TextureCacheBase::TCacheEntryBase* TextureCacheBase::Load(const u32 stage)
TexDecoder_DecodeRGBA8FromTmem(temp, src_data, src_data_gb, expandedWidth, expandedHeight);
}
- entry->Load(temp, width, height, expandedWidth, 0);
+ entry->Load(0, width, height, expandedWidth, temp, decoded_texture_size);
}
iter = textures_by_address.emplace(address, entry);
@@ -837,9 +862,8 @@ TextureCacheBase::TCacheEntryBase* TextureCacheBase::Load(const u32 stage)
for (u32 level_index = 1; level_index != texLevels; ++level_index)
{
const auto& level = hires_tex->m_levels[level_index];
- CheckTempSize(level.data_size);
- memcpy(temp, level.data.get(), level.data_size);
- entry->Load(temp, level.width, level.height, level.width, level_index);
+ entry->Load(level_index, level.width, level.height, level.row_length, level.data.get(),
+ level.data_size);
}
}
else
@@ -877,9 +901,11 @@ TextureCacheBase::TCacheEntryBase* TextureCacheBase::Load(const u32 stage)
}
else
{
+ // No need to call CheckTempSize here, as mips will always be smaller than the base level.
+ size_t decoded_mip_size = expanded_mip_width * sizeof(u32) * expanded_mip_height;
TexDecoder_Decode(temp, mip_src_data, expanded_mip_width, expanded_mip_height, texformat,
tlut, (TlutFormat)tlutfmt);
- entry->Load(temp, mip_width, mip_height, expanded_mip_width, level);
+ entry->Load(level, mip_width, mip_height, expanded_mip_width, temp, decoded_mip_size);
}
mip_src_data += mip_size;
diff --git a/Source/Core/VideoCommon/TextureCacheBase.h b/Source/Core/VideoCommon/TextureCacheBase.h
index 4c9582079b..7f2491a5c7 100644
--- a/Source/Core/VideoCommon/TextureCacheBase.h
+++ b/Source/Core/VideoCommon/TextureCacheBase.h
@@ -27,16 +27,16 @@ public:
bool operator==(const TCacheEntryConfig& o) const
{
- return std::tie(width, height, levels, layers, rendertarget) ==
- std::tie(o.width, o.height, o.levels, o.layers, o.rendertarget);
+ return std::tie(width, height, levels, layers, format, rendertarget) ==
+ std::tie(o.width, o.height, o.levels, o.layers, o.format, o.rendertarget);
}
struct Hasher : std::hash
{
size_t operator()(const TCacheEntryConfig& c) const
{
- u64 id = (u64)c.rendertarget << 63 | (u64)c.layers << 48 | (u64)c.levels << 32 |
- (u64)c.height << 16 | (u64)c.width;
+ u64 id = (u64)c.rendertarget << 63 | (u64)c.format << 50 | (u64)c.layers << 48 |
+ (u64)c.levels << 32 | (u64)c.height << 16 | (u64)c.width;
return std::hash::operator()(id);
}
};
@@ -45,6 +45,7 @@ public:
u32 height = 0;
u32 levels = 1;
u32 layers = 1;
+ HostTextureFormat format = HostTextureFormat::RGBA8;
bool rendertarget = false;
};
@@ -129,7 +130,8 @@ public:
const MathUtil::Rectangle& srcrect,
const MathUtil::Rectangle& dstrect) = 0;
- virtual void Load(const u8* buffer, u32 width, u32 height, u32 expanded_width, u32 level) = 0;
+ virtual void Load(u32 level, u32 width, u32 height, u32 row_length, const u8* buffer,
+ size_t buffer_size) = 0;
virtual void FromRenderTarget(bool is_depth_copy, const EFBRectangle& srcRect, bool scaleByHalf,
unsigned int cbufid, const float* colmat) = 0;
@@ -144,6 +146,10 @@ public:
virtual ~TextureCacheBase(); // needs virtual for DX11 dtor
+ // TODO: Move these to AbstractTexture once it is finished.
+ static bool IsCompressedHostTextureFormat(HostTextureFormat format);
+ static size_t CalculateHostTextureLevelPitch(HostTextureFormat format, u32 row_length);
+
void OnConfigChanged(VideoConfig& config);
// Removes textures which aren't used for more than TEXTURE_KILL_THRESHOLD frames,
diff --git a/Source/Core/VideoCommon/VideoCommon.h b/Source/Core/VideoCommon/VideoCommon.h
index e25b2b66a5..cff2883318 100644
--- a/Source/Core/VideoCommon/VideoCommon.h
+++ b/Source/Core/VideoCommon/VideoCommon.h
@@ -77,6 +77,15 @@ enum class APIType
Nothing
};
+// Texture formats that videocommon can upload/use.
+enum class HostTextureFormat : u32
+{
+ RGBA8,
+ DXT1,
+ DXT3,
+ DXT5
+};
+
inline u32 RGBA8ToRGBA6ToRGBA8(u32 src)
{
u32 color = src;
diff --git a/Source/Core/VideoCommon/VideoCommon.vcxproj b/Source/Core/VideoCommon/VideoCommon.vcxproj
index e742711edf..0a8f60781c 100644
--- a/Source/Core/VideoCommon/VideoCommon.vcxproj
+++ b/Source/Core/VideoCommon/VideoCommon.vcxproj
@@ -69,6 +69,7 @@
+
@@ -185,4 +186,4 @@
-
+
\ No newline at end of file
diff --git a/Source/Core/VideoCommon/VideoCommon.vcxproj.filters b/Source/Core/VideoCommon/VideoCommon.vcxproj.filters
index e9a742a409..ec0150a272 100644
--- a/Source/Core/VideoCommon/VideoCommon.vcxproj.filters
+++ b/Source/Core/VideoCommon/VideoCommon.vcxproj.filters
@@ -164,6 +164,9 @@
Shader Generators
+
+ Util
+
@@ -203,9 +206,6 @@
Decoding
-
- Decoding
-
Register Sections
@@ -321,4 +321,4 @@
-
+
\ No newline at end of file
diff --git a/Source/Core/VideoCommon/VideoConfig.cpp b/Source/Core/VideoCommon/VideoConfig.cpp
index 604bdb4bdd..3865095f4b 100644
--- a/Source/Core/VideoCommon/VideoConfig.cpp
+++ b/Source/Core/VideoCommon/VideoConfig.cpp
@@ -41,6 +41,7 @@ VideoConfig::VideoConfig()
backend_info.bSupportsExclusiveFullscreen = false;
backend_info.bSupportsMultithreading = false;
backend_info.bSupportsInternalResolutionFrameDumps = false;
+ backend_info.bSupportsST3CTextures = false;
bEnableValidationLayer = false;
bBackendMultithreading = true;
diff --git a/Source/Core/VideoCommon/VideoConfig.h b/Source/Core/VideoCommon/VideoConfig.h
index eacb30d8b1..44eac32dc9 100644
--- a/Source/Core/VideoCommon/VideoConfig.h
+++ b/Source/Core/VideoCommon/VideoConfig.h
@@ -197,6 +197,7 @@ struct VideoConfig final
bool bSupportsMultithreading;
bool bSupportsInternalResolutionFrameDumps;
bool bSupportsGPUTextureDecoding;
+ bool bSupportsST3CTextures;
} backend_info;
// Utility