Merge pull request #5279 from stenzek/compressed-custom-textures

Native compressed custom texture support
This commit is contained in:
Stenzek 2017-04-30 00:44:06 +10:00 committed by GitHub
commit a2cba6d72f
37 changed files with 1049 additions and 181 deletions

View File

@ -93,6 +93,7 @@
<ClInclude Include="GL\GLExtensions\ARB_uniform_buffer_object.h" /> <ClInclude Include="GL\GLExtensions\ARB_uniform_buffer_object.h" />
<ClInclude Include="GL\GLExtensions\ARB_vertex_array_object.h" /> <ClInclude Include="GL\GLExtensions\ARB_vertex_array_object.h" />
<ClInclude Include="GL\GLExtensions\ARB_viewport_array.h" /> <ClInclude Include="GL\GLExtensions\ARB_viewport_array.h" />
<ClInclude Include="GL\GLExtensions\EXT_texture_compression_s3tc.h" />
<ClInclude Include="GL\GLExtensions\EXT_texture_filter_anisotropic.h" /> <ClInclude Include="GL\GLExtensions\EXT_texture_filter_anisotropic.h" />
<ClInclude Include="GL\GLExtensions\GLExtensions.h" /> <ClInclude Include="GL\GLExtensions\GLExtensions.h" />
<ClInclude Include="GL\GLExtensions\gl_1_1.h" /> <ClInclude Include="GL\GLExtensions\gl_1_1.h" />
@ -225,4 +226,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets"> <ImportGroup Label="ExtensionTargets">
</ImportGroup> </ImportGroup>
</Project> </Project>

View File

@ -248,6 +248,9 @@
<ClInclude Include="GL\GLExtensions\ARB_compute_shader.h"> <ClInclude Include="GL\GLExtensions\ARB_compute_shader.h">
<Filter>GL\GLExtensions</Filter> <Filter>GL\GLExtensions</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="GL\GLExtensions\EXT_texture_compression_s3tc.h">
<Filter>GL\GLExtensions</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="CDUtils.cpp" /> <ClCompile Include="CDUtils.cpp" />
@ -321,4 +324,4 @@
<ItemGroup> <ItemGroup>
<Natvis Include="BitField.natvis" /> <Natvis Include="BitField.natvis" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -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

View File

@ -31,6 +31,7 @@
#include "Common/GL/GLExtensions/ARB_uniform_buffer_object.h" #include "Common/GL/GLExtensions/ARB_uniform_buffer_object.h"
#include "Common/GL/GLExtensions/ARB_vertex_array_object.h" #include "Common/GL/GLExtensions/ARB_vertex_array_object.h"
#include "Common/GL/GLExtensions/ARB_viewport_array.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/EXT_texture_filter_anisotropic.h"
#include "Common/GL/GLExtensions/HP_occlusion_test.h" #include "Common/GL/GLExtensions/HP_occlusion_test.h"
#include "Common/GL/GLExtensions/KHR_debug.h" #include "Common/GL/GLExtensions/KHR_debug.h"

View File

@ -239,6 +239,19 @@ D3D_FEATURE_LEVEL GetFeatureLevel(IDXGIAdapter* adapter)
return feat_level; 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) HRESULT Create(HWND wnd)
{ {
hWnd = wnd; hWnd = wnd;
@ -427,6 +440,7 @@ HRESULT Create(HWND wnd)
UINT format_support; UINT format_support;
device->CheckFormatSupport(DXGI_FORMAT_B8G8R8A8_UNORM, &format_support); device->CheckFormatSupport(DXGI_FORMAT_B8G8R8A8_UNORM, &format_support);
bgra_textures_supported = (format_support & D3D11_FORMAT_SUPPORT_TEXTURE2D) != 0; bgra_textures_supported = (format_support & D3D11_FORMAT_SUPPORT_TEXTURE2D) != 0;
g_Config.backend_info.bSupportsST3CTextures = SupportsS3TCTextures(device);
stateman = new StateManager; stateman = new StateManager;
return S_OK; return S_OK;

View File

@ -7,6 +7,7 @@
#include <algorithm> #include <algorithm>
#include <memory> #include <memory>
#include "Common/Assert.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
@ -29,6 +30,25 @@ static std::unique_ptr<PSTextureEncoder> g_encoder;
const size_t MAX_COPY_BUFFERS = 32; const size_t MAX_COPY_BUFFERS = 32;
ID3D11Buffer* efbcopycbuf[MAX_COPY_BUFFERS] = {0}; 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() TextureCache::TCacheEntry::~TCacheEntry()
{ {
texture->Release(); texture->Release();
@ -41,6 +61,11 @@ void TextureCache::TCacheEntry::Bind(unsigned int stage)
bool TextureCache::TCacheEntry::Save(const std::string& filename, unsigned int level) 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. // Create a staging/readback texture with the dimensions of the specified mip level.
u32 mip_width = std::max(config.width >> level, 1u); u32 mip_width = std::max(config.width >> level, 1u);
u32 mip_height = std::max(config.height >> level, 1u); u32 mip_height = std::max(config.height >> level, 1u);
@ -129,28 +154,30 @@ void TextureCache::TCacheEntry::CopyRectangleFromTexture(const TCacheEntryBase*
g_renderer->RestoreAPIState(); g_renderer->RestoreAPIState();
} }
void TextureCache::TCacheEntry::Load(const u8* buffer, u32 width, u32 height, u32 expanded_width, void TextureCache::TCacheEntry::Load(u32 level, u32 width, u32 height, u32 row_length,
u32 level) const u8* buffer, size_t buffer_size)
{ {
unsigned int src_pitch = 4 * expanded_width; size_t src_pitch = CalculateHostTextureLevelPitch(config.format, row_length);
D3D::context->UpdateSubresource(texture->GetTex(), level, nullptr, buffer, src_pitch, 0); D3D::context->UpdateSubresource(texture->GetTex(), level, nullptr, buffer,
static_cast<UINT>(src_pitch), 0);
} }
TextureCacheBase::TCacheEntryBase* TextureCache::CreateTexture(const TCacheEntryConfig& config) TextureCacheBase::TCacheEntryBase* TextureCache::CreateTexture(const TCacheEntryConfig& config)
{ {
DXGI_FORMAT dxgi_format = GetDXGIFormatForHostFormat(config.format);
if (config.rendertarget) if (config.rendertarget)
{ {
return new TCacheEntry( return new TCacheEntry(
config, D3DTexture2D::Create( config, D3DTexture2D::Create(config.width, config.height,
config.width, config.height, (D3D11_BIND_FLAG)((int)D3D11_BIND_RENDER_TARGET | (D3D11_BIND_FLAG)((int)D3D11_BIND_RENDER_TARGET |
(int)D3D11_BIND_SHADER_RESOURCE), (int)D3D11_BIND_SHADER_RESOURCE),
D3D11_USAGE_DEFAULT, DXGI_FORMAT_R8G8B8A8_UNORM, 1, config.layers)); D3D11_USAGE_DEFAULT, dxgi_format, 1, config.layers));
} }
else else
{ {
const D3D11_TEXTURE2D_DESC texdesc = const D3D11_TEXTURE2D_DESC texdesc =
CD3D11_TEXTURE2D_DESC(DXGI_FORMAT_R8G8B8A8_UNORM, config.width, config.height, 1, CD3D11_TEXTURE2D_DESC(dxgi_format, config.width, config.height, 1, config.levels,
config.levels, D3D11_BIND_SHADER_RESOURCE, D3D11_USAGE_DEFAULT, 0); D3D11_BIND_SHADER_RESOURCE, D3D11_USAGE_DEFAULT, 0);
ID3D11Texture2D* pTexture; ID3D11Texture2D* pTexture;
const HRESULT hr = D3D::device->CreateTexture2D(&texdesc, nullptr, &pTexture); const HRESULT hr = D3D::device->CreateTexture2D(&texdesc, nullptr, &pTexture);

View File

@ -30,7 +30,8 @@ private:
const MathUtil::Rectangle<int>& srcrect, const MathUtil::Rectangle<int>& srcrect,
const MathUtil::Rectangle<int>& dstrect) override; const MathUtil::Rectangle<int>& 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, void FromRenderTarget(bool is_depth_copy, const EFBRectangle& srcRect, bool scaleByHalf,
unsigned int cbufid, const float* colmat) override; unsigned int cbufid, const float* colmat) override;

View File

@ -77,6 +77,7 @@ void VideoBackend::InitBackendInfo()
g_Config.backend_info.bSupportsMultithreading = false; g_Config.backend_info.bSupportsMultithreading = false;
g_Config.backend_info.bSupportsInternalResolutionFrameDumps = false; g_Config.backend_info.bSupportsInternalResolutionFrameDumps = false;
g_Config.backend_info.bSupportsGPUTextureDecoding = false; g_Config.backend_info.bSupportsGPUTextureDecoding = false;
g_Config.backend_info.bSupportsST3CTextures = false;
IDXGIFactory* factory; IDXGIFactory* factory;
IDXGIAdapter* ad; IDXGIAdapter* ad;

View File

@ -262,6 +262,21 @@ std::vector<DXGI_SAMPLE_DESC> EnumAAModes(ID3D12Device* device)
return aa_modes; 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) HRESULT Create(HWND wnd)
{ {
hWnd = wnd; hWnd = wnd;
@ -478,6 +493,8 @@ HRESULT Create(HWND wnd)
SAFE_RELEASE(factory); SAFE_RELEASE(factory);
SAFE_RELEASE(adapter); SAFE_RELEASE(adapter);
g_Config.backend_info.bSupportsST3CTextures = SupportsS3TCTextures(device12);
return S_OK; return S_OK;
} }

View File

@ -33,6 +33,25 @@ static u32 s_efb_copy_last_cbuf_id = UINT_MAX;
static ID3D12Resource* s_texture_cache_entry_readback_buffer = nullptr; static ID3D12Resource* s_texture_cache_entry_readback_buffer = nullptr;
static size_t s_texture_cache_entry_readback_buffer_size = 0; 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() TextureCache::TCacheEntry::~TCacheEntry()
{ {
m_texture->Release(); 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); Common::AlignUp(level_width * sizeof(u32), D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
size_t required_readback_buffer_size = level_pitch * level_height; 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 // Check if the current readback buffer is large enough
if (required_readback_buffer_size > s_texture_cache_entry_readback_buffer_size) 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(); g_renderer->RestoreAPIState();
} }
void TextureCache::TCacheEntry::Load(const u8* buffer, u32 width, u32 height, u32 expanded_width, void TextureCache::TCacheEntry::Load(u32 level, u32 width, u32 height, u32 row_length,
u32 level) const u8* buffer, size_t buffer_size)
{ {
unsigned int src_pitch = 4 * expanded_width; size_t src_pitch = CalculateHostTextureLevelPitch(config.format, row_length);
D3D::ReplaceRGBATexture2D(m_texture->GetTex12(), buffer, width, height, src_pitch, level, D3D::ReplaceRGBATexture2D(m_texture->GetTex12(), buffer, width, height,
static_cast<unsigned int>(src_pitch), level,
m_texture->GetResourceUsageState()); m_texture->GetResourceUsageState());
} }
TextureCacheBase::TCacheEntryBase* TextureCache::CreateTexture(const TCacheEntryConfig& config) TextureCacheBase::TCacheEntryBase* TextureCache::CreateTexture(const TCacheEntryConfig& config)
{ {
DXGI_FORMAT dxgi_format = GetDXGIFormatForHostFormat(config.format);
if (config.rendertarget) if (config.rendertarget)
{ {
D3DTexture2D* texture = D3DTexture2D* texture =
D3DTexture2D::Create(config.width, config.height, D3DTexture2D::Create(config.width, config.height,
TEXTURE_BIND_FLAG_SHADER_RESOURCE | TEXTURE_BIND_FLAG_RENDER_TARGET, 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); TCacheEntry* entry = new TCacheEntry(config, texture);
@ -204,8 +230,8 @@ TextureCacheBase::TCacheEntryBase* TextureCache::CreateTexture(const TCacheEntry
{ {
ID3D12Resource* texture_resource = nullptr; ID3D12Resource* texture_resource = nullptr;
D3D12_RESOURCE_DESC texture_resource_desc = CD3DX12_RESOURCE_DESC::Tex2D( D3D12_RESOURCE_DESC texture_resource_desc =
DXGI_FORMAT_R8G8B8A8_UNORM, config.width, config.height, 1, config.levels); CD3DX12_RESOURCE_DESC::Tex2D(dxgi_format, config.width, config.height, 1, config.levels);
CheckHR(D3D::device12->CreateCommittedResource( CheckHR(D3D::device12->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT), D3D12_HEAP_FLAG_NONE, &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT), D3D12_HEAP_FLAG_NONE,

View File

@ -39,7 +39,8 @@ private:
const MathUtil::Rectangle<int>& src_rect, const MathUtil::Rectangle<int>& src_rect,
const MathUtil::Rectangle<int>& dst_rect) override; const MathUtil::Rectangle<int>& 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, void FromRenderTarget(bool is_depth_copy, const EFBRectangle& src_rect, bool scale_by_half,
unsigned int cbuf_id, const float* colmat) override; unsigned int cbuf_id, const float* colmat) override;

View File

@ -80,6 +80,7 @@ void VideoBackend::InitBackendInfo()
g_Config.backend_info.bSupportsMultithreading = false; g_Config.backend_info.bSupportsMultithreading = false;
g_Config.backend_info.bSupportsInternalResolutionFrameDumps = false; g_Config.backend_info.bSupportsInternalResolutionFrameDumps = false;
g_Config.backend_info.bSupportsGPUTextureDecoding = false; g_Config.backend_info.bSupportsGPUTextureDecoding = false;
g_Config.backend_info.bSupportsST3CTextures = false;
IDXGIFactory* factory; IDXGIFactory* factory;
IDXGIAdapter* ad; IDXGIAdapter* ad;

View File

@ -45,6 +45,7 @@ void VideoBackend::InitBackendInfo()
g_Config.backend_info.bSupportsMultithreading = false; g_Config.backend_info.bSupportsMultithreading = false;
g_Config.backend_info.bSupportsInternalResolutionFrameDumps = false; g_Config.backend_info.bSupportsInternalResolutionFrameDumps = false;
g_Config.backend_info.bSupportsGPUTextureDecoding = false; g_Config.backend_info.bSupportsGPUTextureDecoding = false;
g_Config.backend_info.bSupportsST3CTextures = false;
// aamodes: We only support 1 sample, so no MSAA // aamodes: We only support 1 sample, so no MSAA
g_Config.backend_info.Adapters.clear(); g_Config.backend_info.Adapters.clear();

View File

@ -31,7 +31,10 @@ private:
{ {
TCacheEntry(const TCacheEntryConfig& _config) : TCacheEntryBase(_config) {} TCacheEntry(const TCacheEntryConfig& _config) : TCacheEntryBase(_config) {}
~TCacheEntry() {} ~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, void FromRenderTarget(bool is_depth_copy, const EFBRectangle& src_rect, bool scale_by_half,
unsigned int cbufid, const float* colmat) override unsigned int cbufid, const float* colmat) override
{ {

View File

@ -462,6 +462,8 @@ Renderer::Renderer()
g_ogl_config.bSupportsConservativeDepth = GLExtensions::Supports("GL_ARB_conservative_depth"); g_ogl_config.bSupportsConservativeDepth = GLExtensions::Supports("GL_ARB_conservative_depth");
g_ogl_config.bSupportsAniso = GLExtensions::Supports("GL_EXT_texture_filter_anisotropic"); 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.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) if (GLInterface->GetMode() == GLInterfaceMode::MODE_OPENGLES3)
{ {

View File

@ -11,6 +11,7 @@
#include <memory> #include <memory>
#include <vector> #include <vector>
#include "Common/Assert.h"
#include "Common/GL/GLInterfaceBase.h" #include "Common/GL/GLInterfaceBase.h"
#include "Common/MsgHandler.h" #include "Common/MsgHandler.h"
#include "Common/StringUtil.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); 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() TextureCache::TCacheEntry::~TCacheEntry()
{ {
if (texture) if (texture)
@ -129,6 +172,11 @@ void TextureCache::TCacheEntry::Bind(unsigned int stage)
bool TextureCache::TCacheEntry::Save(const std::string& filename, unsigned int level) 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); 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) if (g_ogl_config.bSupportsTextureStorage)
{ {
glTexStorage3D(GL_TEXTURE_2D_ARRAY, config.levels, GL_RGBA8, config.width, config.height, GLenum gl_internal_format = GetGLInternalFormatForTextureFormat(config.format, true);
config.layers); glTexStorage3D(GL_TEXTURE_2D_ARRAY, config.levels, gl_internal_format, config.width,
config.height, config.layers);
} }
if (config.rendertarget) if (config.rendertarget)
{ {
// We can't render to compressed formats.
_assert_(!IsCompressedHostTextureFormat(config.format));
if (!g_ogl_config.bSupportsTextureStorage) if (!g_ogl_config.bSupportsTextureStorage)
{ {
for (u32 level = 0; level < config.levels; level++) for (u32 level = 0; level < config.levels; level++)
@ -202,8 +253,8 @@ void TextureCache::TCacheEntry::CopyRectangleFromTexture(const TCacheEntryBase*
g_renderer->RestoreAPIState(); g_renderer->RestoreAPIState();
} }
void TextureCache::TCacheEntry::Load(const u8* buffer, u32 width, u32 height, u32 expanded_width, void TextureCache::TCacheEntry::Load(u32 level, u32 width, u32 height, u32 row_length,
u32 level) const u8* buffer, size_t buffer_size)
{ {
if (level >= config.levels) if (level >= config.levels)
PanicAlert("Texture only has %d levels, can't update level %d", config.levels, level); 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); glActiveTexture(GL_TEXTURE9);
glBindTexture(GL_TEXTURE_2D_ARRAY, texture); glBindTexture(GL_TEXTURE_2D_ARRAY, texture);
if (expanded_width != width) if (row_length != width)
glPixelStorei(GL_UNPACK_ROW_LENGTH, expanded_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, if (g_ogl_config.bSupportsTextureStorage)
GL_UNSIGNED_BYTE, buffer); {
glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, level, 0, 0, 0, width, height, 1,
gl_internal_format, static_cast<GLsizei>(buffer_size), buffer);
}
else
{
glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, level, gl_internal_format, width, height, 1, 0,
static_cast<GLsizei>(buffer_size), buffer);
}
} }
else else
{ {
glTexImage3D(GL_TEXTURE_2D_ARRAY, level, GL_RGBA, width, height, 1, 0, GL_RGBA, GLenum gl_format = GetGLFormatForTextureFormat(config.format);
GL_UNSIGNED_BYTE, buffer); 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); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
TextureCache::SetStage(); TextureCache::SetStage();

View File

@ -45,7 +45,8 @@ private:
const MathUtil::Rectangle<int>& srcrect, const MathUtil::Rectangle<int>& srcrect,
const MathUtil::Rectangle<int>& dstrect) override; const MathUtil::Rectangle<int>& 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, void FromRenderTarget(bool is_depth_copy, const EFBRectangle& srcRect, bool scaleByHalf,
unsigned int cbufid, const float* colmat) override; unsigned int cbufid, const float* colmat) override;

View File

@ -103,6 +103,7 @@ void VideoBackend::InitBackendInfo()
g_Config.backend_info.bSupportsPaletteConversion = true; g_Config.backend_info.bSupportsPaletteConversion = true;
g_Config.backend_info.bSupportsClipControl = true; g_Config.backend_info.bSupportsClipControl = true;
g_Config.backend_info.bSupportsDepthClamp = true; g_Config.backend_info.bSupportsDepthClamp = true;
g_Config.backend_info.bSupportsST3CTextures = false;
g_Config.backend_info.Adapters.clear(); g_Config.backend_info.Adapters.clear();

View File

@ -65,7 +65,10 @@ private:
{ {
TCacheEntry(const TCacheEntryConfig& _config) : TCacheEntryBase(_config) {} TCacheEntry(const TCacheEntryConfig& _config) : TCacheEntryBase(_config) {}
~TCacheEntry() {} ~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, void FromRenderTarget(bool is_depth_copy, const EFBRectangle& srcRect, bool scaleByHalf,
unsigned int cbufid, const float* colmat) override unsigned int cbufid, const float* colmat) override
{ {
@ -134,6 +137,7 @@ void VideoSoftware::InitBackendInfo()
g_Config.backend_info.bSupportsComputeShaders = false; g_Config.backend_info.bSupportsComputeShaders = false;
g_Config.backend_info.bSupportsInternalResolutionFrameDumps = false; g_Config.backend_info.bSupportsInternalResolutionFrameDumps = false;
g_Config.backend_info.bSupportsGPUTextureDecoding = false; g_Config.backend_info.bSupportsGPUTextureDecoding = false;
g_Config.backend_info.bSupportsST3CTextures = false;
// aamodes // aamodes
g_Config.backend_info.AAModes = {1}; g_Config.backend_info.AAModes = {1};

View File

@ -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 // 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 // large anyway, so it's only really an issue for HD texture packs, and memory is not
// a limiting factor in these scenarios anyway. // 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 // Streaming uniform buffer size
constexpr size_t INITIAL_UNIFORM_STREAM_BUFFER_SIZE = 16 * 1024 * 1024; constexpr size_t INITIAL_UNIFORM_STREAM_BUFFER_SIZE = 16 * 1024 * 1024;

View File

@ -127,7 +127,7 @@ void StagingBuffer::InvalidateCPUCache(VkDeviceSize offset, VkDeviceSize size)
void StagingBuffer::Read(VkDeviceSize offset, void* data, size_t size, bool invalidate_caches) void StagingBuffer::Read(VkDeviceSize offset, void* data, size_t size, bool invalidate_caches)
{ {
_assert_((offset + size) <= m_size); _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) if (invalidate_caches)
InvalidateCPUCache(offset, size); InvalidateCPUCache(offset, size);
@ -138,7 +138,7 @@ void StagingBuffer::Write(VkDeviceSize offset, const void* data, size_t size,
bool invalidate_caches) bool invalidate_caches)
{ {
_assert_((offset + size) <= m_size); _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); memcpy(m_map_pointer + (offset - m_map_offset), data, size);
if (invalidate_caches) if (invalidate_caches)

View File

@ -9,6 +9,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "Common/Align.h"
#include "Common/Assert.h" #include "Common/Assert.h"
#include "Common/CommonFuncs.h" #include "Common/CommonFuncs.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
@ -238,9 +239,10 @@ TextureCacheBase::TCacheEntryBase* TextureCache::CreateTexture(const TCacheEntry
usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
// Allocate texture object // Allocate texture object
VkFormat vk_format = Util::GetVkFormatForHostTextureFormat(config.format);
std::unique_ptr<Texture2D> texture = Texture2D::Create( std::unique_ptr<Texture2D> texture = Texture2D::Create(
config.width, config.height, config.levels, config.layers, TEXTURECACHE_TEXTURE_FORMAT, config.width, config.height, config.levels, config.layers, vk_format, VK_SAMPLE_COUNT_1_BIT,
VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_VIEW_TYPE_2D_ARRAY, VK_IMAGE_TILING_OPTIMAL, usage); VK_IMAGE_VIEW_TYPE_2D_ARRAY, VK_IMAGE_TILING_OPTIMAL, usage);
if (!texture) if (!texture)
return nullptr; return nullptr;
@ -341,8 +343,8 @@ TextureCache::TCacheEntry::~TCacheEntry()
g_command_buffer_mgr->DeferFramebufferDestruction(m_framebuffer); g_command_buffer_mgr->DeferFramebufferDestruction(m_framebuffer);
} }
void TextureCache::TCacheEntry::Load(const u8* buffer, unsigned int width, unsigned int height, void TextureCache::TCacheEntry::Load(u32 level, u32 width, u32 height, u32 row_length,
unsigned int expanded_width, unsigned int level) const u8* buffer, size_t buffer_size)
{ {
// Can't copy data larger than the texture extents. // Can't copy data larger than the texture extents.
width = std::max(1u, std::min(width, m_texture->GetWidth() >> level)); 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(), m_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentInitCommandBuffer(),
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
// Does this texture data fit within the streaming buffer? // For unaligned textures, we can save some memory in the transfer buffer by skipping the rows
u32 upload_width = width; // that lie outside of the texture's dimensions.
u32 upload_pitch = upload_width * sizeof(u32);
u32 upload_size = upload_pitch * height;
u32 upload_alignment = static_cast<u32>(g_vulkan_context->GetBufferImageGranularity()); u32 upload_alignment = static_cast<u32>(g_vulkan_context->GetBufferImageGranularity());
u32 source_pitch = expanded_width * 4; u32 block_size = Util::GetBlockSize(m_texture->GetFormat());
if ((upload_size + upload_alignment) <= STAGING_TEXTURE_UPLOAD_THRESHOLD && u32 num_rows = Common::AlignUp(height, block_size) / block_size;
(upload_size + upload_alignment) <= MAXIMUM_TEXTURE_UPLOAD_BUFFER_SIZE) size_t source_pitch = CalculateHostTextureLevelPitch(config.format, row_length);
{ size_t upload_size = source_pitch * num_rows;
// Assume tightly packed rows, with no padding as the buffer source. std::unique_ptr<StagingBuffer> temp_buffer;
StreamBuffer* upload_buffer = TextureCache::GetInstance()->m_texture_upload_buffer.get(); VkBuffer upload_buffer;
VkDeviceSize upload_buffer_offset;
// Allocate memory from the streaming buffer for the texture data. // Does this texture data fit within the streaming buffer?
if (!upload_buffer->ReserveMemory(upload_size, g_vulkan_context->GetBufferImageGranularity())) 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. // Execute the command buffer first.
WARN_LOG(VIDEO, "Executing command list while waiting for space in texture upload buffer"); WARN_LOG(VIDEO, "Executing command list while waiting for space in texture upload buffer");
Util::ExecuteCurrentCommandsAndRestoreState(false); Util::ExecuteCurrentCommandsAndRestoreState(false);
// Try allocating again. This may cause a fence wait. // 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"); PanicAlert("Failed to allocate space in texture upload buffer");
} }
// Grab buffer pointers // Copy to the streaming buffer.
VkBuffer image_upload_buffer = upload_buffer->GetBuffer(); upload_buffer = stream_buffer->GetBuffer();
VkDeviceSize image_upload_buffer_offset = upload_buffer->GetCurrentOffset(); upload_buffer_offset = stream_buffer->GetCurrentOffset();
u8* image_upload_buffer_pointer = upload_buffer->GetCurrentHostPointer(); std::memcpy(stream_buffer->GetCurrentHostPointer(), buffer, upload_size);
stream_buffer->CommitMemory(upload_size);
// 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);
} }
else else
{ {
// Slow path. The data for the image is too large to fit in the streaming buffer, so we need // Create a temporary staging buffer that is destroyed after the image is copied.
// to allocate a temporary texture to store the data in, then copy to the real texture. temp_buffer = StagingBuffer::Create(STAGING_BUFFER_TYPE_UPLOAD, upload_size,
std::unique_ptr<StagingTexture2D> staging_texture = StagingTexture2D::Create( VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
STAGING_BUFFER_TYPE_UPLOAD, width, height, TEXTURECACHE_TEXTURE_FORMAT); if (!temp_buffer || !temp_buffer->Map())
if (!staging_texture || !staging_texture->Map())
{ {
PanicAlert("Failed to allocate staging texture for large texture upload."); PanicAlert("Failed to allocate staging texture for large texture upload.");
return; return;
} }
// Copy data to staging texture first, then to the "real" texture. upload_buffer = temp_buffer->GetBuffer();
staging_texture->WriteTexels(0, 0, width, height, buffer, source_pitch); upload_buffer_offset = 0;
staging_texture->CopyToImage(g_command_buffer_mgr->GetCurrentInitCommandBuffer(), temp_buffer->Write(0, buffer, upload_size, true);
m_texture->GetImage(), VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, width, temp_buffer->Unmap();
height, level, 0);
} }
// 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, 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); _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. // Determine dimensions of image we want to save.
u32 level_width = std::max(1u, config.width >> level); u32 level_width = std::max(1u, config.width >> level);
u32 level_height = std::max(1u, config.height >> 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 // 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. // we blocked until the copy completed on the GPU anyway.
bool result = TextureToPng(reinterpret_cast<u8*>(staging_texture->GetMapPointer()), bool result = TextureToPng(reinterpret_cast<u8*>(staging_texture->GetMapPointer()),
staging_texture->GetRowStride(), filename, level_width, level_height); static_cast<u32>(staging_texture->GetRowStride()), filename,
level_width, level_height);
staging_texture->Unmap(); staging_texture->Unmap();
return result; return result;

View File

@ -27,8 +27,8 @@ public:
Texture2D* GetTexture() const { return m_texture.get(); } Texture2D* GetTexture() const { return m_texture.get(); }
VkFramebuffer GetFramebuffer() const { return m_framebuffer; } VkFramebuffer GetFramebuffer() const { return m_framebuffer; }
void Load(const u8* buffer, unsigned int width, unsigned int height, void Load(u32 level, u32 width, u32 height, u32 row_length, const u8* buffer,
unsigned int expanded_width, unsigned int level) override; size_t buffer_size) override;
void FromRenderTarget(bool is_depth_copy, const EFBRectangle& src_rect, bool scale_by_half, void FromRenderTarget(bool is_depth_copy, const EFBRectangle& src_rect, bool scale_by_half,
unsigned int cbufid, const float* colmat) override; unsigned int cbufid, const float* colmat) override;
void CopyRectangleFromTexture(const TCacheEntryBase* source, void CopyRectangleFromTexture(const TCacheEntryBase* source,

View File

@ -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) VkFormat GetLinearFormat(VkFormat format)
{ {
switch (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) u32 GetTexelSize(VkFormat format)
{ {
// Only contains pixel formats we use. // Only contains pixel formats we use.
@ -91,12 +124,33 @@ u32 GetTexelSize(VkFormat format)
case VK_FORMAT_B8G8R8A8_UNORM: case VK_FORMAT_B8G8R8A8_UNORM:
return 4; 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: default:
PanicAlert("Unhandled pixel format"); PanicAlert("Unhandled pixel format");
return 1; 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 ClampRect2D(const VkRect2D& rect, u32 width, u32 height)
{ {
VkRect2D out; VkRect2D out;

View File

@ -25,8 +25,11 @@ size_t AlignBufferOffset(size_t offset, size_t alignment);
u32 MakeRGBA8Color(float r, float g, float b, float a); u32 MakeRGBA8Color(float r, float g, float b, float a);
bool IsDepthFormat(VkFormat format); bool IsDepthFormat(VkFormat format);
bool IsCompressedFormat(VkFormat format);
VkFormat GetLinearFormat(VkFormat format); VkFormat GetLinearFormat(VkFormat format);
VkFormat GetVkFormatForHostTextureFormat(HostTextureFormat format);
u32 GetTexelSize(VkFormat format); u32 GetTexelSize(VkFormat format);
u32 GetBlockSize(VkFormat format);
// Clamps a VkRect2D to the specified dimensions. // Clamps a VkRect2D to the specified dimensions.
VkRect2D ClampRect2D(const VkRect2D& rect, u32 width, u32 height); VkRect2D ClampRect2D(const VkRect2D& rect, u32 width, u32 height);

View File

@ -245,6 +245,7 @@ void VulkanContext::PopulateBackendInfo(VideoConfig* config)
config->backend_info.bSupportsFragmentStoresAndAtomics = false; // Dependent on features. config->backend_info.bSupportsFragmentStoresAndAtomics = false; // Dependent on features.
config->backend_info.bSupportsSSAA = false; // Dependent on features. config->backend_info.bSupportsSSAA = false; // Dependent on features.
config->backend_info.bSupportsDepthClamp = 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. 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 = config->backend_info.bSupportsDepthClamp =
(features.depthClamp == VK_TRUE && features.shaderClipDistance == VK_TRUE); (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. // 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. // 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)) if (DriverDetails::HasBug(DriverDetails::BUG_PRIMITIVE_RESTART))
@ -459,6 +463,7 @@ bool VulkanContext::SelectDeviceFeatures()
m_device_features.occlusionQueryPrecise = available_features.occlusionQueryPrecise; m_device_features.occlusionQueryPrecise = available_features.occlusionQueryPrecise;
m_device_features.shaderClipDistance = available_features.shaderClipDistance; m_device_features.shaderClipDistance = available_features.shaderClipDistance;
m_device_features.depthClamp = available_features.depthClamp; m_device_features.depthClamp = available_features.depthClamp;
m_device_features.textureCompressionBC = available_features.textureCompressionBC;
return true; return true;
} }

View File

@ -14,6 +14,7 @@ set(SRCS
GeometryShaderGen.cpp GeometryShaderGen.cpp
GeometryShaderManager.cpp GeometryShaderManager.cpp
HiresTextures.cpp HiresTextures.cpp
HiresTextures_DDSLoader.cpp
ImageWrite.cpp ImageWrite.cpp
IndexGenerator.cpp IndexGenerator.cpp
LightingShaderGen.cpp LightingShaderGen.cpp

View File

@ -366,6 +366,21 @@ std::string HiresTexture::GenBaseName(const u8* texture, size_t texture_size, co
return name; 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> HiresTexture::Search(const u8* texture, size_t texture_size, std::shared_ptr<HiresTexture> HiresTexture::Search(const u8* texture, size_t texture_size,
const u8* tlut, size_t tlut_size, u32 width, const u8* tlut, size_t tlut_size, u32 width,
u32 height, int format, bool has_mipmaps) u32 height, int format, bool has_mipmaps)
@ -395,81 +410,134 @@ std::shared_ptr<HiresTexture> HiresTexture::Search(const u8* texture, size_t tex
std::unique_ptr<HiresTexture> HiresTexture::Load(const std::string& base_filename, u32 width, std::unique_ptr<HiresTexture> HiresTexture::Load(const std::string& base_filename, u32 width,
u32 height) u32 height)
{ {
std::unique_ptr<HiresTexture> ret; // We need to have a level 0 custom texture to even consider loading.
for (int level = 0;; level++) 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<HiresTexture> ret = std::unique_ptr<HiresTexture>(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<u32>(ret->m_levels.size());; mip_level++)
{ {
std::string filename = base_filename; std::string filename = base_filename;
if (level) if (mip_level != 0)
{ filename += StringFromFormat("_mip%u", mip_level);
filename += StringFromFormat("_mip%u", level);
}
if (s_textureMap.find(filename) != s_textureMap.end()) filename_iter = s_textureMap.find(filename);
{ if (filename_iter == s_textureMap.end())
Level l; 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::IOFile file;
file.Open(s_textureMap[filename], "rb"); file.Open(filename_iter->second, "rb");
std::vector<u8> buffer(file.GetSize()); std::vector<u8> buffer(file.GetSize());
file.ReadBytes(buffer.data(), file.GetSize()); file.ReadBytes(buffer.data(), file.GetSize());
if (!LoadTexture(level, buffer))
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)
{ {
ERROR_LOG(VIDEO, "Custom texture %s failed to load", filename.c_str()); ERROR_LOG(VIDEO, "Custom texture %s failed to load", filename.c_str());
break; break;
} }
}
if (!level) ret->m_levels.push_back(std::move(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;
}
if (!ret) // If we failed to load any mip levels, we can't use this texture at all.
ret = std::unique_ptr<HiresTexture>(new HiresTexture); if (ret->m_levels.empty())
ret->m_levels.push_back(std::move(l)); return nullptr;
// no more mipmaps available // Verify that the aspect ratio of the texture hasn't changed, as this could have side-effects.
if (width == 1 && height == 1) const Level& first_mip = ret->m_levels[0];
break; 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 // Same deal if the custom texture isn't a multiple of the native size.
width = std::max(1u, width >> 1); if (width != 0 && height != 0 && (first_mip.width % width || first_mip.height % height))
height = std::max(1u, height >> 1); {
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<u32>(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 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; return ret;
} }
bool HiresTexture::LoadTexture(Level& level, const std::vector<u8>& buffer)
{
int channels;
int width;
int height;
u8* data = SOIL_load_image_from_memory(buffer.data(), static_cast<int>(buffer.size()), &width,
&height, &channels, SOIL_LOAD_RGBA);
if (!data)
return false;
// Images loaded by SOIL are converted to RGBA.
level.width = static_cast<u32>(width);
level.height = static_cast<u32>(height);
level.format = HostTextureFormat::RGBA8;
level.data = ImageDataPointer(data, SOIL_free_image_data);
level.row_length = level.width;
level.data_size = static_cast<size_t>(level.row_length) * 4 * level.height;
return true;
}
std::string HiresTexture::GetTextureDirectory(const std::string& game_id) std::string HiresTexture::GetTextureDirectory(const std::string& game_id)
{ {
const std::string texture_directory = File::GetUserPath(D_HIRESTEXTURES_IDX) + 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() HiresTexture::~HiresTexture()
{ {
} }
HostTextureFormat HiresTexture::GetFormat() const
{
return m_levels.at(0).format;
}

View File

@ -9,11 +9,12 @@
#include <vector> #include <vector>
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "VideoCommon/VideoCommon.h"
class HiresTexture class HiresTexture
{ {
public: public:
using SOILPointer = std::unique_ptr<u8, void (*)(unsigned char*)>; using ImageDataPointer = std::unique_ptr<u8, void (*)(unsigned char*)>;
static void Init(); static void Init();
static void Update(); static void Update();
@ -27,22 +28,30 @@ public:
size_t tlut_size, u32 width, u32 height, int format, size_t tlut_size, u32 width, u32 height, int format,
bool has_mipmaps, bool dump = false); bool has_mipmaps, bool dump = false);
static u32 CalculateMipCount(u32 width, u32 height);
~HiresTexture(); ~HiresTexture();
HostTextureFormat GetFormat() const;
struct Level struct Level
{ {
Level(); Level();
SOILPointer data; ImageDataPointer data;
size_t data_size = 0; HostTextureFormat format = HostTextureFormat::RGBA8;
u32 width = 0; u32 width = 0;
u32 height = 0; u32 height = 0;
u32 row_length = 0;
size_t data_size = 0;
}; };
std::vector<Level> m_levels; std::vector<Level> m_levels;
private: private:
static std::unique_ptr<HiresTexture> Load(const std::string& base_filename, u32 width, static std::unique_ptr<HiresTexture> Load(const std::string& base_filename, u32 width,
u32 height); 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<u8>& buffer);
static void Prefetch(); static void Prefetch();
static std::string GetTextureDirectory(const std::string& game_id); static std::string GetTextureDirectory(const std::string& game_id);

View File

@ -0,0 +1,486 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "VideoCommon/HiresTextures.h"
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <functional>
#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<void(HiresTexture::Level*)> 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<size_t>(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<size_t>(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<size_t>(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);
}

View File

@ -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<size_t>(std::max(1u, row_length / 4)) * 8;
case HostTextureFormat::DXT3:
case HostTextureFormat::DXT5:
return static_cast<size_t>(std::max(1u, row_length / 4)) * 16;
case HostTextureFormat::RGBA8:
default:
return static_cast<size_t>(row_length) * 4;
}
}
void TextureCacheBase::CheckTempSize(size_t required_size) void TextureCacheBase::CheckTempSize(size_t required_size)
{ {
if (required_size <= temp_size) if (required_size <= temp_size)
@ -750,8 +773,6 @@ TextureCacheBase::TCacheEntryBase* TextureCacheBase::Load(const u32 stage)
} }
expandedWidth = level.width; expandedWidth = level.width;
expandedHeight = level.height; 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.width = width;
config.height = height; config.height = height;
config.levels = texLevels; config.levels = texLevels;
config.format = hires_tex ? hires_tex->GetFormat() : HostTextureFormat::RGBA8;
TCacheEntryBase* entry = AllocateTexture(config); TCacheEntryBase* entry = AllocateTexture(config);
GFX_DEBUGGER_PAUSE_AT(NEXT_NEW_TEXTURE, true); GFX_DEBUGGER_PAUSE_AT(NEXT_NEW_TEXTURE, true);
@ -784,17 +806,20 @@ TextureCacheBase::TCacheEntryBase* TextureCacheBase::Load(const u32 stage)
const u8* tlut = &texMem[tlutaddr]; const u8* tlut = &texMem[tlutaddr];
if (hires_tex) 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); u32 row_stride = bytes_per_block * (expandedWidth / bsw);
g_texture_cache->DecodeTextureOnGPU( g_texture_cache->DecodeTextureOnGPU(
entry, 0, src_data, texture_size, static_cast<TextureFormat>(texformat), width, height, entry, 0, src_data, texture_size, static_cast<TextureFormat>(texformat), width, height,
expandedWidth, expandedHeight, row_stride, tlut, static_cast<TlutFormat>(tlutfmt)); expandedWidth, expandedHeight, row_stride, tlut, static_cast<TlutFormat>(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)) if (!(texformat == GX_TF_RGBA8 && from_tmem))
{ {
TexDecoder_Decode(temp, src_data, expandedWidth, expandedHeight, texformat, tlut, 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); 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); 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) for (u32 level_index = 1; level_index != texLevels; ++level_index)
{ {
const auto& level = hires_tex->m_levels[level_index]; const auto& level = hires_tex->m_levels[level_index];
CheckTempSize(level.data_size); entry->Load(level_index, level.width, level.height, level.row_length, level.data.get(),
memcpy(temp, level.data.get(), level.data_size); level.data_size);
entry->Load(temp, level.width, level.height, level.width, level_index);
} }
} }
else else
@ -877,9 +901,11 @@ TextureCacheBase::TCacheEntryBase* TextureCacheBase::Load(const u32 stage)
} }
else 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, TexDecoder_Decode(temp, mip_src_data, expanded_mip_width, expanded_mip_height, texformat,
tlut, (TlutFormat)tlutfmt); 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; mip_src_data += mip_size;

View File

@ -27,16 +27,16 @@ public:
bool operator==(const TCacheEntryConfig& o) const bool operator==(const TCacheEntryConfig& o) const
{ {
return std::tie(width, height, levels, layers, rendertarget) == return std::tie(width, height, levels, layers, format, rendertarget) ==
std::tie(o.width, o.height, o.levels, o.layers, o.rendertarget); std::tie(o.width, o.height, o.levels, o.layers, o.format, o.rendertarget);
} }
struct Hasher : std::hash<u64> struct Hasher : std::hash<u64>
{ {
size_t operator()(const TCacheEntryConfig& c) const size_t operator()(const TCacheEntryConfig& c) const
{ {
u64 id = (u64)c.rendertarget << 63 | (u64)c.layers << 48 | (u64)c.levels << 32 | u64 id = (u64)c.rendertarget << 63 | (u64)c.format << 50 | (u64)c.layers << 48 |
(u64)c.height << 16 | (u64)c.width; (u64)c.levels << 32 | (u64)c.height << 16 | (u64)c.width;
return std::hash<u64>::operator()(id); return std::hash<u64>::operator()(id);
} }
}; };
@ -45,6 +45,7 @@ public:
u32 height = 0; u32 height = 0;
u32 levels = 1; u32 levels = 1;
u32 layers = 1; u32 layers = 1;
HostTextureFormat format = HostTextureFormat::RGBA8;
bool rendertarget = false; bool rendertarget = false;
}; };
@ -129,7 +130,8 @@ public:
const MathUtil::Rectangle<int>& srcrect, const MathUtil::Rectangle<int>& srcrect,
const MathUtil::Rectangle<int>& dstrect) = 0; const MathUtil::Rectangle<int>& 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, virtual void FromRenderTarget(bool is_depth_copy, const EFBRectangle& srcRect, bool scaleByHalf,
unsigned int cbufid, const float* colmat) = 0; unsigned int cbufid, const float* colmat) = 0;
@ -144,6 +146,10 @@ public:
virtual ~TextureCacheBase(); // needs virtual for DX11 dtor 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); void OnConfigChanged(VideoConfig& config);
// Removes textures which aren't used for more than TEXTURE_KILL_THRESHOLD frames, // Removes textures which aren't used for more than TEXTURE_KILL_THRESHOLD frames,

View File

@ -77,6 +77,15 @@ enum class APIType
Nothing Nothing
}; };
// Texture formats that videocommon can upload/use.
enum class HostTextureFormat : u32
{
RGBA8,
DXT1,
DXT3,
DXT5
};
inline u32 RGBA8ToRGBA6ToRGBA8(u32 src) inline u32 RGBA8ToRGBA6ToRGBA8(u32 src)
{ {
u32 color = src; u32 color = src;

View File

@ -69,6 +69,7 @@
<ClCompile Include="FPSCounter.cpp" /> <ClCompile Include="FPSCounter.cpp" />
<ClCompile Include="FramebufferManagerBase.cpp" /> <ClCompile Include="FramebufferManagerBase.cpp" />
<ClCompile Include="HiresTextures.cpp" /> <ClCompile Include="HiresTextures.cpp" />
<ClCompile Include="HiresTextures_DDSLoader.cpp" />
<ClCompile Include="ImageWrite.cpp" /> <ClCompile Include="ImageWrite.cpp" />
<ClCompile Include="IndexGenerator.cpp" /> <ClCompile Include="IndexGenerator.cpp" />
<ClCompile Include="MainBase.cpp" /> <ClCompile Include="MainBase.cpp" />
@ -185,4 +186,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets"> <ImportGroup Label="ExtensionTargets">
</ImportGroup> </ImportGroup>
</Project> </Project>

View File

@ -164,6 +164,9 @@
<ClCompile Include="LightingShaderGen.cpp"> <ClCompile Include="LightingShaderGen.cpp">
<Filter>Shader Generators</Filter> <Filter>Shader Generators</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="HiresTextures_DDSLoader.cpp">
<Filter>Util</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="CommandProcessor.h" /> <ClInclude Include="CommandProcessor.h" />
@ -203,9 +206,6 @@
<ClInclude Include="TextureDecoder.h"> <ClInclude Include="TextureDecoder.h">
<Filter>Decoding</Filter> <Filter>Decoding</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="TextureDecoder_Util.h">
<Filter>Decoding</Filter>
</ClInclude>
<ClInclude Include="BPFunctions.h"> <ClInclude Include="BPFunctions.h">
<Filter>Register Sections</Filter> <Filter>Register Sections</Filter>
</ClInclude> </ClInclude>
@ -321,4 +321,4 @@
<ItemGroup> <ItemGroup>
<Text Include="CMakeLists.txt" /> <Text Include="CMakeLists.txt" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -41,6 +41,7 @@ VideoConfig::VideoConfig()
backend_info.bSupportsExclusiveFullscreen = false; backend_info.bSupportsExclusiveFullscreen = false;
backend_info.bSupportsMultithreading = false; backend_info.bSupportsMultithreading = false;
backend_info.bSupportsInternalResolutionFrameDumps = false; backend_info.bSupportsInternalResolutionFrameDumps = false;
backend_info.bSupportsST3CTextures = false;
bEnableValidationLayer = false; bEnableValidationLayer = false;
bBackendMultithreading = true; bBackendMultithreading = true;

View File

@ -197,6 +197,7 @@ struct VideoConfig final
bool bSupportsMultithreading; bool bSupportsMultithreading;
bool bSupportsInternalResolutionFrameDumps; bool bSupportsInternalResolutionFrameDumps;
bool bSupportsGPUTextureDecoding; bool bSupportsGPUTextureDecoding;
bool bSupportsST3CTextures;
} backend_info; } backend_info;
// Utility // Utility