Merge pull request #5279 from stenzek/compressed-custom-textures
Native compressed custom texture support
This commit is contained in:
commit
a2cba6d72f
|
@ -93,6 +93,7 @@
|
|||
<ClInclude Include="GL\GLExtensions\ARB_uniform_buffer_object.h" />
|
||||
<ClInclude Include="GL\GLExtensions\ARB_vertex_array_object.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\GLExtensions.h" />
|
||||
<ClInclude Include="GL\GLExtensions\gl_1_1.h" />
|
||||
|
@ -225,4 +226,4 @@
|
|||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
|
@ -248,6 +248,9 @@
|
|||
<ClInclude Include="GL\GLExtensions\ARB_compute_shader.h">
|
||||
<Filter>GL\GLExtensions</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="GL\GLExtensions\EXT_texture_compression_s3tc.h">
|
||||
<Filter>GL\GLExtensions</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="CDUtils.cpp" />
|
||||
|
@ -321,4 +324,4 @@
|
|||
<ItemGroup>
|
||||
<Natvis Include="BitField.natvis" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
|
@ -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
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
#include "Common/Assert.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
|
||||
|
@ -29,6 +30,25 @@ static std::unique_ptr<PSTextureEncoder> 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<UINT>(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);
|
||||
|
|
|
@ -30,7 +30,8 @@ private:
|
|||
const MathUtil::Rectangle<int>& srcrect,
|
||||
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,
|
||||
unsigned int cbufid, const float* colmat) override;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -262,6 +262,21 @@ std::vector<DXGI_SAMPLE_DESC> 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<unsigned int>(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,
|
||||
|
|
|
@ -39,7 +39,8 @@ private:
|
|||
const MathUtil::Rectangle<int>& src_rect,
|
||||
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,
|
||||
unsigned int cbuf_id, const float* colmat) override;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#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<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
|
||||
{
|
||||
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();
|
||||
|
|
|
@ -45,7 +45,8 @@ private:
|
|||
const MathUtil::Rectangle<int>& srcrect,
|
||||
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,
|
||||
unsigned int cbufid, const float* colmat) override;
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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<Texture2D> 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<u32>(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<StagingBuffer> 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<StagingTexture2D> 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<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();
|
||||
return result;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ set(SRCS
|
|||
GeometryShaderGen.cpp
|
||||
GeometryShaderManager.cpp
|
||||
HiresTextures.cpp
|
||||
HiresTextures_DDSLoader.cpp
|
||||
ImageWrite.cpp
|
||||
IndexGenerator.cpp
|
||||
LightingShaderGen.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> 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> HiresTexture::Search(const u8* texture, size_t tex
|
|||
std::unique_ptr<HiresTexture> HiresTexture::Load(const std::string& base_filename, u32 width,
|
||||
u32 height)
|
||||
{
|
||||
std::unique_ptr<HiresTexture> 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<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;
|
||||
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<u8> 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<HiresTexture>(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<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
|
||||
{
|
||||
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<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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -9,11 +9,12 @@
|
|||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "VideoCommon/VideoCommon.h"
|
||||
|
||||
class HiresTexture
|
||||
{
|
||||
public:
|
||||
using SOILPointer = std::unique_ptr<u8, void (*)(unsigned char*)>;
|
||||
using ImageDataPointer = std::unique_ptr<u8, void (*)(unsigned char*)>;
|
||||
|
||||
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<Level> m_levels;
|
||||
|
||||
private:
|
||||
static std::unique_ptr<HiresTexture> 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<u8>& buffer);
|
||||
static void Prefetch();
|
||||
|
||||
static std::string GetTextureDirectory(const std::string& game_id);
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
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<TextureFormat>(texformat), width, height,
|
||||
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))
|
||||
{
|
||||
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;
|
||||
|
|
|
@ -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<u64>
|
||||
{
|
||||
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<u64>::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<int>& srcrect,
|
||||
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,
|
||||
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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -69,6 +69,7 @@
|
|||
<ClCompile Include="FPSCounter.cpp" />
|
||||
<ClCompile Include="FramebufferManagerBase.cpp" />
|
||||
<ClCompile Include="HiresTextures.cpp" />
|
||||
<ClCompile Include="HiresTextures_DDSLoader.cpp" />
|
||||
<ClCompile Include="ImageWrite.cpp" />
|
||||
<ClCompile Include="IndexGenerator.cpp" />
|
||||
<ClCompile Include="MainBase.cpp" />
|
||||
|
@ -185,4 +186,4 @@
|
|||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
|
@ -164,6 +164,9 @@
|
|||
<ClCompile Include="LightingShaderGen.cpp">
|
||||
<Filter>Shader Generators</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HiresTextures_DDSLoader.cpp">
|
||||
<Filter>Util</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="CommandProcessor.h" />
|
||||
|
@ -203,9 +206,6 @@
|
|||
<ClInclude Include="TextureDecoder.h">
|
||||
<Filter>Decoding</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="TextureDecoder_Util.h">
|
||||
<Filter>Decoding</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="BPFunctions.h">
|
||||
<Filter>Register Sections</Filter>
|
||||
</ClInclude>
|
||||
|
@ -321,4 +321,4 @@
|
|||
<ItemGroup>
|
||||
<Text Include="CMakeLists.txt" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
|
@ -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;
|
||||
|
|
|
@ -197,6 +197,7 @@ struct VideoConfig final
|
|||
bool bSupportsMultithreading;
|
||||
bool bSupportsInternalResolutionFrameDumps;
|
||||
bool bSupportsGPUTextureDecoding;
|
||||
bool bSupportsST3CTextures;
|
||||
} backend_info;
|
||||
|
||||
// Utility
|
||||
|
|
Loading…
Reference in New Issue