17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 15:39:20 +02:00
Files
maszyna/betterRenderer/renderer/source/nvtexture.cpp
maj00r 26501b0fd7 hoist standard includes above everything else
Ordering them before the imports in the same file was not enough: a
project header on line 1 can import a module several levels down whose
global module fragment already pulled in the same standard header, and
that include guard does not cross the module boundary. Putting them
first means the guard is set before anything can import.

Skips vendored single-header libraries and anything inside an #if.
2026-09-10 01:02:17 +02:00

544 lines
18 KiB
C++

#include <array>
#include <cstddef>
#include <cstdint>
#include <fstream>
#include <ios>
#include <memory>
#include <sstream>
#include <string>
#include <utility>
#include "nvtexture.h"
#include "nvrenderer/nvrenderer.h"
#include "utilities/Globals_macros.h"
#include <fmt/format.h>
#include "utilities/utilities_macros.h"
#include "nvrendererbackend.h"
#include "stbi/stb_image.h"
#include "stbi/stb_image_resize2.h"
import eu07.utilities.globals;
import eu07.scripting.pyint;
import eu07.application.application;
import eu07.utilities.dictionary;
import eu07.utilities.utilities;
import eu07.utilities.logs;
// #include "Texture.h"
#define STB_IMAGE_RESIZE2_IMPLEMENTATION
import eu07.model.texture;
#undef STB_IMAGE_RESIZE2_IMPLEMENTATION
uint64_t NvTexture::s_change_counter = 0;
void NvTexture::Load(int size_bias) {
if (m_sz_texture->is_stub()) {
m_width = 2;
m_height = 2;
auto &[pitch, data] = m_data.emplace_back();
pitch = m_width * 4;
m_format = nvrhi::Format::RGBA8_UNORM;
data.resize(pitch * m_height, '\0');
return;
}
std::string buf{};
{
std::ifstream file(fmt::format("{}{}", m_sz_texture->get_name().data(),
m_sz_texture->get_type().data()),
std::ios::binary | std::ios::ate);
buf.resize(file.tellg(), '\0');
file.seekg(0);
file.read(buf.data(), buf.size());
};
stbi_set_flip_vertically_on_load(true);
if (LoadDDS(buf, size_bias)) {
} else if (int channels; stbi_uc *source = stbi_load_from_memory(
reinterpret_cast<stbi_uc const *>(buf.data()),
buf.size(), &m_width, &m_height, &channels, 4)) {
int mipcount =
static_cast<int>(floor(log2(std::max(m_width, m_height)))) + 1;
m_has_alpha = channels == 4;
m_format =
m_srgb ? nvrhi::Format::SRGBA8_UNORM : nvrhi::Format::RGBA8_UNORM;
int width = m_width, prev_width = m_width, height = m_height,
prev_height = m_height;
for (int i = 0; i < mipcount; ++i) {
auto &[pitch, data] = m_data.emplace_back();
pitch = width * 4;
data.resize(pitch * height, '\0');
if (!i) {
memcpy(data.data(), source, data.size());
} else {
stbir_resize(m_data[i - 1].second.data(), prev_width, prev_height,
prev_width * 4, data.data(), width, height, width * 4,
STBIR_4CHANNEL,
m_srgb ? STBIR_TYPE_UINT8_SRGB : STBIR_TYPE_UINT8,
STBIR_EDGE_WRAP, STBIR_FILTER_MITCHELL);
}
prev_width = std::exchange(width, std::max(1, width >> 1));
prev_height = std::exchange(height, std::max(1, height >> 1));
}
stbi_image_free(source);
} else if (m_sz_texture->get_type() == ".tga") {
std::stringstream ss(buf, std::ios::in | std::ios::binary);
// Read the header of the TGA, compare it with the known headers for
// compressed and uncompressed TGAs
unsigned char tgaheader[18];
ss.read((char *)tgaheader, sizeof(unsigned char) * 18);
while (tgaheader[0] > 0) {
--tgaheader[0];
unsigned char temp;
ss.read((char *)&temp, sizeof(unsigned char));
}
m_width = tgaheader[13] * 256 + tgaheader[12];
m_height = tgaheader[15] * 256 + tgaheader[14];
m_format =
m_srgb ? nvrhi::Format::SRGBA8_UNORM : nvrhi::Format::RGBA8_UNORM;
int const bytesperpixel = tgaheader[16] / 8;
// check whether width, height an BitsPerPixel are valid
if ((m_width <= 0) || (m_height <= 0) ||
((bytesperpixel != 1) && (bytesperpixel != 3) &&
(bytesperpixel != 4))) {
return;
}
m_has_alpha = false;
auto &[pitch, data] = m_data.emplace_back();
pitch = m_width * 4;
// allocate the data buffer
int const datasize = pitch * m_height;
data.resize(datasize);
// call the appropriate loader-routine
if (tgaheader[2] == 2) {
// uncompressed TGA
// rgb or greyscale image, expand to bgra
unsigned char buffer[4] = {255, 255, 255,
255}; // alpha channel will be white
unsigned int *datapointer = (unsigned int *)&data[0];
unsigned int *bufferpointer = (unsigned int *)&buffer[0];
int const pixelcount = m_width * m_height;
for (int i = 0; i < pixelcount; ++i) {
ss.read((char *)&buffer[0], sizeof(unsigned char) * bytesperpixel);
if (bytesperpixel == 1) {
// expand greyscale data
buffer[1] = buffer[0];
buffer[2] = buffer[0];
}
std::swap(buffer[0], buffer[2]);
// copy all four values in one operation
(*datapointer) = (*bufferpointer);
++datapointer;
}
} else if (tgaheader[2] == 10) {
// compressed TGA
int currentpixel = 0;
unsigned char buffer[4] = {255, 255, 255, 255};
const int pixelcount = m_width * m_height;
unsigned int *datapointer = (unsigned int *)&data[0];
unsigned int *bufferpointer = (unsigned int *)&buffer[0];
do {
unsigned char chunkheader = 0;
ss.read((char *)&chunkheader, sizeof(unsigned char));
if ((chunkheader & 0x80) == 0) {
// if the high bit is not set, it means it is the number of RAW color
// packets, plus 1
for (int i = 0; i <= chunkheader; ++i) {
ss.read((char *)&buffer[0], bytesperpixel);
m_has_alpha |= bytesperpixel == 4 && buffer[3] != 255;
if (bytesperpixel == 1) {
// expand greyscale data
buffer[1] = buffer[0];
buffer[2] = buffer[0];
}
std::swap(buffer[0], buffer[2]);
// copy all four values in one operation
(*datapointer) = (*bufferpointer);
++datapointer;
++currentpixel;
}
} else {
// rle chunk, the color supplied afterwards is reapeated header + 1
// times (not including the highest bit)
chunkheader &= ~0x80;
// read the current color
ss.read((char *)&buffer[0], bytesperpixel);
m_has_alpha |= bytesperpixel == 4 && buffer[3] != 255;
if (bytesperpixel == 1) {
// expand greyscale data
buffer[1] = buffer[0];
buffer[2] = buffer[0];
}
std::swap(buffer[0], buffer[2]);
// copy the color into the image data as many times as dictated
for (int i = 0; i <= chunkheader; ++i) {
(*datapointer) = (*bufferpointer);
++datapointer;
++currentpixel;
}
}
} while (currentpixel < pixelcount);
}
}
int max_size = Global.iMaxTextureSize >> size_bias;
while (m_data.size() > 1) {
if (m_width <= max_size && m_height <= max_size) {
break;
}
m_width = std::max(m_width >> 1, 1);
m_height = std::max(m_height >> 1, 1);
m_data.erase(m_data.begin());
}
}
void NvTexture::set_components_hint(int format_hint) {
m_srgb = format_hint == GL_SRGB || format_hint == GL_SRGB8 ||
format_hint == GL_SRGB_ALPHA || format_hint == GL_SRGB8_ALPHA8;
}
void NvTexture::make_from_memory(size_t width, size_t height,
const uint8_t *data) {
m_data.clear();
m_rhi_texture = nullptr;
auto &[pitch, slice] = m_data.emplace_back();
m_format = nvrhi::Format::RGBA8_UNORM;
pitch = 4 * width;
slice.resize(pitch * height);
memcpy(slice.data(), data, slice.size());
}
void NvTexture::update_from_memory(size_t width, size_t height,
const uint8_t *data) {
auto renderer = dynamic_cast<NvRenderer *>(GfxRenderer.get());
if (!renderer) return;
auto backend = renderer->GetBackend();
if (width != m_width || height != m_height ||
m_format != nvrhi::Format::RGBA8_UNORM || !m_rhi_texture) {
m_data.clear();
m_width = width;
m_height = height;
auto &[pitch, slice] = m_data.emplace_back();
m_format = nvrhi::Format::RGBA8_UNORM;
pitch = 4 * width;
slice.resize(pitch * height);
m_rhi_texture = backend->GetDevice()->createTexture(
nvrhi::TextureDesc()
.setDebugName(std::string(m_sz_texture->get_name()))
.setWidth(m_width)
.setHeight(m_height)
.setMipLevels(m_data.size())
.setFormat(m_format)
.setInitialState(nvrhi::ResourceStates::ShaderResource)
.setKeepInitialState(true));
m_last_change = ++s_change_counter;
}
auto &slice = std::get<1>(m_data.front());
memcpy(slice.data(), data, slice.size());
nvrhi::CommandListHandle command_list =
backend->GetDevice()->createCommandList(
nvrhi::CommandListParameters()
.setQueueType(nvrhi::CommandQueue::Graphics)
.setEnableImmediateExecution(false));
command_list->open();
for (int mip = 0; mip < m_data.size(); ++mip) {
const auto &[pitch, data] = m_data[mip];
command_list->writeTexture(m_rhi_texture, 0, mip, data.data(), pitch);
}
command_list->close();
backend->GetDevice()->executeCommandList(command_list,
nvrhi::CommandQueue::Graphics);
}
bool NvTexture::IsLoaded() const { return false; }
NvTextureManager::NvTextureManager(NvRenderer *renderer)
: m_backend(renderer->m_backend.get()) {}
size_t NvTextureManager::FetchTexture(std::string path, int format_hint,
int size_bias, bool unload_on_location) {
if (contains(path, '|')) {
path.erase(path.find('|')); // po | może być nazwa kolejnej tekstury
}
std::pair<std::string, std::string> locator; // resource name, resource type
std::string traits;
// discern textures generated by a script
// TBD: support file: for file resources?
auto const isgenerated{path.find("make:") == 0};
auto const isinternalsrc{path.find("internal_src:") == 0};
// process supplied resource name
if (isgenerated || isinternalsrc) {
// generated resource
// scheme:(user@)path?query
// remove scheme indicator
path.erase(0, path.find(':') + 1);
// TODO: extract traits specification from the query
// clean up slashes
erase_leading_slashes(path);
} else {
// regular file resource
// (filepath/)(#)filename.extension(:traits)
// extract trait specifications
auto const traitpos = path.rfind(':');
if (traitpos != std::string::npos) {
// po dwukropku mogą być podane dodatkowe informacje niebędące nazwą
// tekstury
if (path.size() > traitpos + 1) {
traits = path.substr(traitpos + 1);
}
path.erase(traitpos);
}
// potentially trim file type indicator since we check for multiple types
erase_extension(path);
// clean up slashes
erase_leading_slashes(path);
path = ToLower(path);
// temporary code for legacy assets -- textures with names beginning with #
// are to be sharpened
if (path.starts_with("#") || contains(path, "/#")) {
traits += '#';
}
}
// try to locate requested texture in the databank
// TBD, TODO: include trait specification in the resource id in the databank?
{
const std::array<std::string, 3> filenames{
Global.asCurrentTexturePath + path, path, paths::textures + path};
for (const auto &filename : filenames) {
if (auto found = m_texture_map.find(filename);
found != m_texture_map.end())
return found->second;
}
}
// if the lookup fails...
if (isgenerated) {
// TODO: verify presence of the generator script
locator.first = path;
locator.second = "make:";
} else if (isinternalsrc) {
locator.first = path;
locator.second = "internalsrc:";
} else {
// ...for file resources check if it's on disk
locator = texture_manager::find_on_disk(path);
if (true == locator.first.empty()) {
// there's nothing matching in the databank nor on the disk, report
// failure
ErrorLog("Bad file: failed to locate texture file \"" + path + "\"",
logtype::file);
return 0;
}
}
auto &new_texture = m_texture_cache.emplace_back();
auto index = m_texture_cache.size();
m_texture_map.emplace(locator.first, index);
new_texture = std::make_shared<NvTexture>();
auto sz_texture = std::make_shared<opengl_texture>();
new_texture->m_sz_texture = sz_texture;
sz_texture->is_texstub = isgenerated;
sz_texture->name = locator.first;
sz_texture->type = locator.second;
sz_texture->traits = traits;
sz_texture->components_hint = format_hint;
new_texture->set_components_hint(format_hint);
new_texture->Load(size_bias);
WriteLog("Created texture object for \"" + locator.first + "\"",
logtype::texture);
if (unload_on_location) {
new_texture->m_gc_slot = m_unloadable_textures.size();
auto &gc_slot = m_unloadable_textures.emplace_back();
gc_slot.m_index = index - 1;
} else {
new_texture->m_gc_slot = static_cast<size_t>(-1);
}
return index;
}
size_t NvTextureManager::RegisterExternalTexture(std::string const& path,
nvrhi::ITexture *texture) {
auto [it, added] = m_texture_map.emplace(path, -1);
if (added) {
m_texture_cache.emplace_back(std::make_shared<NvTexture>());
it->second = m_texture_cache.size();
}
auto cache = m_texture_cache[it->second - 1];
cache->m_rhi_texture = texture;
cache->m_last_change = ++NvTexture::s_change_counter;
return it->second;
}
bool NvTexture::CreateRhiTexture() {
if (m_rhi_texture) return true;
m_last_change = ++s_change_counter;
auto renderer = dynamic_cast<NvRenderer *>(GfxRenderer.get());
if (!renderer) return false;
auto backend = renderer->GetBackend();
m_rhi_texture = renderer->GetBackend()->GetDevice()->createTexture(
nvrhi::TextureDesc()
.setDebugName(std::string(m_sz_texture->get_name()))
.setWidth(m_width)
.setHeight(m_height)
.setMipLevels(m_data.size())
.setFormat(m_format)
.setInitialState(nvrhi::ResourceStates::ShaderResource)
.setKeepInitialState(true));
nvrhi::CommandListHandle command_list =
backend->GetDevice()->createCommandList(
nvrhi::CommandListParameters()
.setQueueType(nvrhi::CommandQueue::Graphics)
.setEnableImmediateExecution(false));
command_list->open();
for (int mip = 0; mip < m_data.size(); ++mip) {
const auto &[pitch, data] = m_data[mip];
command_list->writeTexture(m_rhi_texture, 0, mip, data.data(), pitch);
}
command_list->close();
backend->GetDevice()->executeCommandList(command_list,
nvrhi::CommandQueue::Graphics);
if (m_sz_texture->get_type() == "make:") {
auto const components{Split(m_sz_texture->get_name(), '?')};
auto dictionary = std::make_shared<dictionary_source>(components.back());
auto rt = std::make_shared<python_rt>();
rt->shared_tex = this;
if (!Application.request(
{ToLower(components.front()), dictionary, rt})) /*__debugbreak()*/
;
}
return true;
}
bool NvTextureManager::IsValidHandle(size_t handle) {
return handle > 0 && handle <= m_texture_cache.size();
}
NvTexture *NvTextureManager::GetTexture(size_t handle) {
if (!handle || handle > m_texture_cache.size()) {
return nullptr;
}
return m_texture_cache[handle - 1].get();
}
nvrhi::ITexture *NvTextureManager::GetRhiTexture(
size_t handle, nvrhi::ICommandList *command_list) {
if (!IsValidHandle(handle)) {
return nullptr;
}
const auto &texture = m_texture_cache[handle - 1];
// if (!texture.m_rhi_texture) {
// texture.m_rhi_texture = m_backend->GetDevice()->createTexture(
// nvrhi::TextureDesc()
// .setDebugName(texture.m_sz_texture.name)
// .setWidth(texture.m_width)
// .setHeight(texture.m_height)
// .setMipLevels(texture.m_mipcount)
// .setFormat(texture.m_format)
// .setInitialState(nvrhi::ResourceStates::ShaderResource)
// .setKeepInitialState(true));
//
// for (int mip = 0; mip < texture.m_mipcount; ++mip) {
// const auto &[pitch, data] = texture.m_data[mip];
// command_list->writeTexture(texture.m_rhi_texture, 0, mip, data.data(),
// pitch);
// }
// }
texture->CreateRhiTexture();
return texture->m_rhi_texture;
}
TextureTraitFlags NvTextureManager::GetTraits(size_t handle) {
if (!IsValidHandle(handle)) {
return 0;
}
return m_texture_cache[handle - 1]->GetTraits();
}
void NvTextureManager::UpdateLastUse(size_t handle,
const glm::dvec3 &location) {
if (!IsValidHandle(handle)) {
return;
}
const auto &texture = m_texture_cache[handle - 1];
if (texture->m_gc_slot < m_unloadable_textures.size()) {
m_unloadable_textures[texture->m_gc_slot].m_last_position_requested =
location;
}
}
nvrhi::SamplerHandle NvTextureManager::GetSamplerForTraits(
TextureTraitFlags traits, NvRenderer::RenderPassType pass) {
switch (pass) {
case NvRenderer::RenderPassType::CubeMap:
case NvRenderer::RenderPassType::ShadowMap:
traits[MaTextureTraits_Sharpen] = false;
traits[MaTextureTraits_NoAnisotropy] = true;
traits[MaTextureTraits_NoMipBias] = true;
break;
}
auto &sampler = m_samplers[traits];
if (!sampler) {
nvrhi::SamplerDesc desc =
nvrhi::SamplerDesc()
.setAddressU(traits[MaTextureTraits_ClampS]
? nvrhi::SamplerAddressMode::Clamp
: nvrhi::SamplerAddressMode::Wrap)
.setAddressV(traits[MaTextureTraits_ClampT]
? nvrhi::SamplerAddressMode::Clamp
: nvrhi::SamplerAddressMode::Wrap)
.setAllFilters(!traits[MaTextureTraits_NoFilter])
.setMipFilter(false)
.setMaxAnisotropy(traits[MaTextureTraits_NoAnisotropy] ||
traits[MaTextureTraits_NoFilter]
? 0.f
: 16.f)
.setMipBias(traits[MaTextureTraits_NoMipBias] ||
traits[MaTextureTraits_NoFilter]
? 0.f
: -1.76f);
sampler = m_backend->GetDevice()->createSampler(desc);
}
return sampler;
}
void NvTextureManager::Cleanup(const glm::dvec3 &location) {
for (const auto &slot : m_unloadable_textures) {
if (glm::distance2(slot.m_last_position_requested, location) >
Global.BaseDrawRange * Global.BaseDrawRange) {
m_texture_cache[slot.m_index]->m_rhi_texture = nullptr;
}
}
}