1
0
forked from 0ad/0ad
0ad/source/graphics/TextureManager.h

382 lines
12 KiB
C++

/* Copyright (C) 2023 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_TEXTUREMANAGER
#define INCLUDED_TEXTUREMANAGER
#include "graphics/Texture.h"
#include "lib/file/vfs/vfs.h"
#include "lib/tex/tex.h"
#include "renderer/backend/IDevice.h"
#include "renderer/backend/IDeviceCommandContext.h"
#include "renderer/backend/ITexture.h"
#include <memory>
class CTextureProperties;
class CTextureManagerImpl;
/**
* Texture manager with asynchronous loading and automatic DDS conversion/compression.
*
* Input textures can be any format. They will be converted to DDS using settings defined
* in files named "texture.xml", in the same directory as the texture and in its parent
* directories. See CTextureConverter for the XML syntax. The DDS file will be cached
* for faster loading in the future.
*
* Typically the graphics code will initialise many textures at the start of the game,
* mostly for off-screen objects, by calling CreateTexture().
* Loading texture data may be very slow (especially if it needs to be converted
* to DDS), and we don't want the game to become unresponsive.
* CreateTexture therefore returns an object immediately, without loading the
* texture. If the object is never used then the data will never be loaded.
*
* Typically, the renderer will call CTexture::Bind() when it wants to use the
* texture. This will trigger the loading of the texture data. If it can be loaded
* quickly (i.e. there is already a cached DDS version), then it will be loaded before
* the function returns, and the texture can be rendered as normal.
*
* If loading will take a long time, then Bind() binds a default placeholder texture
* and starts loading the texture in the background. It will use the correct texture
* when the renderer next calls Bind() after the load has finished.
*
* It is also possible to prefetch textures which are not being rendered yet, but
* are expected to be rendered soon (e.g. for off-screen terrain tiles).
* These will be loaded in the background, when there are no higher-priority textures
* to load.
*
* The same texture file can be safely loaded multiple times with different backend parameters
* (but this should be avoided whenever possible, as it wastes VRAM).
*
* For release packages, DDS files can be precached by appending ".dds" to their name,
* which will be used instead of doing runtime conversion. This means most players should
* never experience the slow asynchronous conversion behaviour.
* These cache files will typically be packed into an archive for faster loading;
* if no archive cache is available then the source file will be converted and stored
* as a loose cache file on the user's disk.
*/
class CTextureManager
{
NONCOPYABLE(CTextureManager);
public:
/**
* Construct texture manager. vfs must be the VFS instance used for all textures
* loaded from this object.
* highQuality is slower and intended for batch-conversion modes.
*/
CTextureManager(PIVFS vfs, bool highQuality, Renderer::Backend::IDevice* device);
~CTextureManager();
/**
* Create a texture with the given properties.
* The texture data will not be loaded immediately.
*/
CTexturePtr CreateTexture(const CTextureProperties& props);
/**
* Wraps a backend texture.
*/
CTexturePtr WrapBackendTexture(
std::unique_ptr<Renderer::Backend::ITexture> backendTexture);
/**
* Returns a magenta texture. Use this for highlighting errors
* (e.g. missing terrain textures).
*/
const CTexturePtr& GetErrorTexture();
/**
* Returns a single color RGBA texture with CColor(1.0f, 1.0f, 1.0f, 1.0f).
*/
const CTexturePtr& GetWhiteTexture();
/**
* Returns a single color RGBA texture with CColor(0.0f, 0.0f, 0.0f, 0.0f).
*/
const CTexturePtr& GetTransparentTexture();
/**
* Returns a white RGBA texture with alpha gradient.
*/
const CTexturePtr& GetAlphaGradientTexture();
/**
* Returns a single color RGBA texture cube with CColor(0.0f, 0.0f, 0.0f, 1.0f).
*/
const CTexturePtr& GetBlackTextureCube();
/**
* Work on asynchronous texture loading operations, if any.
* Returns true if it did any work.
* The caller should typically loop this per frame until it returns
* false or exceeds the allocated time for this frame.
*/
bool MakeProgress();
/**
* Work on asynchronous texture uploading operations, if any.
* Returns true if it did any work. Mostly the same as MakeProgress.
*/
bool MakeUploadProgress(Renderer::Backend::IDeviceCommandContext* deviceCommandContext);
/**
* Synchronously converts and compresses and saves the texture,
* and returns the output path (minus a "cache/" prefix). This
* is intended for pre-caching textures in release archives.
* @return true on success
*/
bool GenerateCachedTexture(const VfsPath& path, VfsPath& outputPath);
/**
* @return a cached version of the path
*/
VfsPath GetCachedPath(const VfsPath& path) const;
/**
* Returns true if the given texture exists.
* This tests both for the original and converted filename.
*/
bool TextureExists(const VfsPath& path) const;
/**
* Returns total number of bytes uploaded for all current texture.
*/
size_t GetBytesUploaded() const;
/**
* Should be called on any quality or anisotropic change.
*/
void OnQualityChanged();
private:
CTextureManagerImpl* m;
};
/**
* Represents the filename and GL parameters of a texture,
* for passing to CTextureManager::CreateTexture.
*/
class CTextureProperties
{
friend class CTextureManagerImpl;
friend struct TextureCacheCmp;
friend struct TPequal_to;
friend struct TPhash;
public:
/**
* Use the given texture name, and default GL parameters.
*/
explicit CTextureProperties(const VfsPath& path)
: m_Path(path)
{
}
CTextureProperties(
const VfsPath& path, const Renderer::Backend::Format formatOverride)
: m_Path(path), m_FormatOverride(formatOverride)
{
}
/**
* Set sampler address mode.
*/
void SetAddressMode(const Renderer::Backend::Sampler::AddressMode addressMode)
{
m_AddressModeU = m_AddressModeV = addressMode;
}
/**
* Set sampler address mode separately for different coordinates.
*/
void SetAddressMode(
const Renderer::Backend::Sampler::AddressMode addressModeU,
const Renderer::Backend::Sampler::AddressMode addressModeV)
{
m_AddressModeU = addressModeU;
m_AddressModeV = addressModeV;
}
/**
* The value of max anisotropy is set by options. Though it might make sense
* to add an override.
*/
void SetAnisotropicFilter(const bool enabled) { m_AnisotropicFilterEnabled = enabled; }
// TODO: rather than this static definition of texture properties
// (especially anisotropy), maybe we want something that can be more
// easily tweaked in an Options menu? e.g. the caller just specifies
// "terrain texture mode" and we combine it with the user's options.
// That'd let us dynamically change texture properties easily.
//
// enum EQualityMode
// {
// NONE,
// TERRAIN,
// MODEL,
// GUI
// }
// void SetQuality(EQualityMode mode, float anisotropy, float lodbias, int reducemipmaps, ...);
//
// or something a bit like that.
void SetIgnoreQuality(bool ignore) { m_IgnoreQuality = ignore; }
private:
// Must update TPhash, TPequal_to when changing these fields
VfsPath m_Path;
Renderer::Backend::Sampler::AddressMode m_AddressModeU =
Renderer::Backend::Sampler::AddressMode::REPEAT;
Renderer::Backend::Sampler::AddressMode m_AddressModeV =
Renderer::Backend::Sampler::AddressMode::REPEAT;
bool m_AnisotropicFilterEnabled = false;
Renderer::Backend::Format m_FormatOverride =
Renderer::Backend::Format::UNDEFINED;
bool m_IgnoreQuality = false;
};
/**
* Represents a texture object.
* The texture data may or may not have been loaded yet.
* Before it has been loaded, all operations will act on a default
* 1x1-pixel grey texture instead.
*/
class CTexture
{
NONCOPYABLE(CTexture);
public:
~CTexture();
/**
* Returns the width (in pixels) of the current texture.
*/
size_t GetWidth() const;
/**
* Returns the height (in pixels) of the current texture.
*/
size_t GetHeight() const;
/**
* Returns whether the current texture has an alpha channel.
*/
bool HasAlpha() const;
/**
* Returns the ARGB value of the lowest mipmap level (i.e. the
* average of the whole texture).
* Returns 0 if the texture has no mipmaps.
*/
u32 GetBaseColor() const;
/**
* Returns total number of bytes uploaded for this texture.
*/
size_t GetUploadedSize() const;
/**
* Uploads a texture data to a backend texture if successfully loaded.
* If the texture data hasn't been loaded yet, this may wait a short while to
* load it. If loading takes too long then it will return sooner and the data will
* be loaded in a background thread, so this does not guarantee the texture really
* will be uploaded.
*/
void UploadBackendTextureIfNeeded(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext);
/**
* Returns a backend texture if successfully uploaded, else fallback.
*/
Renderer::Backend::ITexture* GetBackendTexture();
const Renderer::Backend::ITexture* GetBackendTexture() const;
/**
* Attempt to load the texture data quickly, as with
* GetUploadedBackendTextureIfNeeded(). Returns whether the texture data is
* currently loaded (but not uploaded).
*/
bool TryLoad();
/**
* Returns whether the texture data is currently loaded.
*/
bool IsLoaded() const { return m_State == LOADED; }
/**
* Returns whether the texture data is currently uploaded.
*/
bool IsUploaded() const { return m_State == UPLOADED; }
/**
* Activate the prefetching optimisation for this texture.
* Use this if it is likely the texture will be needed in the near future.
* It will be loaded in the background so that it is likely to be ready when
* it is used by Bind().
*/
void Prefetch();
private:
friend class CTextureManagerImpl;
friend class CPredefinedTexture;
friend struct TextureCacheCmp;
friend struct TPequal_to;
friend struct TPhash;
// Only the texture manager can create these
explicit CTexture(
std::unique_ptr<Renderer::Backend::ITexture> texture,
Renderer::Backend::ITexture* fallback,
const CTextureProperties& props, CTextureManagerImpl* textureManager);
void ResetBackendTexture(
std::unique_ptr<Renderer::Backend::ITexture> backendTexture,
Renderer::Backend::ITexture* fallbackBackendTexture);
const CTextureProperties m_Properties;
std::unique_ptr<Renderer::Backend::ITexture> m_BackendTexture;
// It's possible to m_FallbackBackendTexture references m_BackendTexture.
Renderer::Backend::ITexture* m_FallbackBackendTexture = nullptr;
u32 m_BaseColor;
std::unique_ptr<Tex> m_TextureData;
size_t m_UploadedSize = 0;
uint32_t m_BaseLevelOffset = 0;
enum
{
UNLOADED, // loading has not started
PREFETCH_NEEDS_LOADING, // was prefetched; currently waiting to try loading from cache
PREFETCH_NEEDS_CONVERTING, // was prefetched; currently waiting to be sent to the texture converter
PREFETCH_IS_CONVERTING, // was prefetched; currently being processed by the texture converter
HIGH_NEEDS_CONVERTING, // high-priority; currently waiting to be sent to the texture converter
HIGH_IS_CONVERTING, // high-priority; currently being processed by the texture converter
LOADED, // loading texture data has completed (successfully or not)
UPLOADED // uploading to backend has completed (successfully or not)
} m_State;
CTextureManagerImpl* m_TextureManager;
// Self-reference to let us recover the CTexturePtr for this object.
// (weak pointer to avoid cycles)
std::weak_ptr<CTexture> m_Self;
};
#endif // INCLUDED_TEXTUREMANAGER