Completely rework texture loading.

This commit is contained in:
2024-06-10 04:27:09 -04:00
parent 4e0c223edc
commit 8e37f20c65
25 changed files with 500 additions and 518 deletions

View File

@@ -12,6 +12,7 @@
#include <iostream>
#include "KeyboardMovementController.hpp"
#include "assets/stores.hpp"
#include "engine/Average.hpp"
#include "engine/buffers/UniqueFrameSuballocation.hpp"
#include "engine/debug/drawers.hpp"
@@ -39,6 +40,19 @@ namespace fgl::engine
static Average< float, 60 * 15 > rolling_ms_average;
void preStage( vk::CommandBuffer& cmd_buffer )
{
ZoneScopedN( "Pre-Stage" );
getTextureStore().stage( cmd_buffer );
}
void postStage()
{
ZoneScopedN( "Post-Stage" );
getTextureStore().confirmStaged();
}
void EngineContext::run()
{
TracyCZoneN( TRACY_PrepareEngine, "Inital Run", true );
@@ -54,12 +68,9 @@ namespace fgl::engine
PerFrameSuballocation< HostSingleT< PointLight > > point_lights { global_ubo_buffer,
SwapChain::MAX_FRAMES_IN_FLIGHT };
Texture debug_tex { Texture::loadFromFile( "models/textures/DebugTexture.png" ) };
Sampler sampler { vk::Filter::eLinear,
vk::Filter::eLinear,
vk::SamplerMipmapMode::eLinear,
vk::SamplerAddressMode::eClampToEdge };
debug_tex.getImageView().getSampler() = std::move( sampler );
std::shared_ptr< Texture > debug_tex {
getTextureStore().load( "models/textures/DebugTexture.png", vk::Format::eR8G8B8A8Unorm )
};
constexpr std::uint32_t matrix_default_size { 64_MiB };
constexpr std::uint32_t draw_parameter_default_size { 64_MiB };
@@ -146,6 +157,8 @@ namespace fgl::engine
if ( auto command_buffer = m_renderer.beginFrame(); command_buffer )
{
preStage( command_buffer );
ZoneScopedN( "Render" );
//Update
const std::uint16_t frame_index { m_renderer.getFrameIndex() };
@@ -194,6 +207,8 @@ namespace fgl::engine
FrameMark;
}
postStage();
}
Device::getInstance().device().waitIdle();
@@ -259,6 +274,7 @@ namespace fgl::engine
}
}*/
/*
{
ZoneScopedN( "Load phyiscs test" );
std::vector< std::shared_ptr< Model > > models { Model::createModelsFromScene(
@@ -312,6 +328,7 @@ namespace fgl::engine
m_game_objects_root.addGameObject( std::move( floor ) );
}
*/
/*
{

View File

@@ -0,0 +1,18 @@
//
// Created by kj16609 on 6/6/24.
//
#include "AssetManager.hpp"
#include "stores.hpp"
namespace fgl::engine
{
inline static TextureStore tex_store {};
TextureStore& getTextureStore()
{
return tex_store;
}
} // namespace fgl::engine

View File

@@ -0,0 +1,117 @@
//
// Created by kj16609 on 6/6/24.
//
#pragma once
#include <vulkan/vulkan.hpp>
#include <filesystem>
#include <memory>
#include <queue>
namespace fgl::engine
{
template < typename T >
class AssetStore;
template < typename T >
struct AssetInterface
{
//! Stages the asset to the device (GPU)
virtual void stage( vk::CommandBuffer& buffer ) = 0;
friend class AssetStore< T >;
bool m_been_staged { false };
public:
//! Is the Asset ready to be used. (Returns false if not staged)
inline bool isReady() const { return m_been_staged; }
inline void setReady() { m_been_staged = true; };
virtual ~AssetInterface() = default;
};
template < typename T >
class AssetStore
{
static_assert(
std::is_base_of_v< AssetInterface< T >, T >, "AssetStore<T, TKey>: T must inherit from AssetInterface" );
//! Items that are actively in use.
//std::unordered_map< TKey, std::weak_ptr< T > > active_map {};
//! Assets needing to be staged
//TODO: ASYNC Ring buffer queue
std::queue< std::shared_ptr< T > > to_stage;
//TODO: Add tracy monitor to mutex
std::mutex queue_mtx {};
//! Assets currently being staged.
std::vector< std::shared_ptr< T > > processing {};
public:
//TODO: Come up with a better design the the loading function.
/// We should have a way to prevent a asset from being loaded multiple times.
template < typename... T_Args >
//TODO: This genuinely seems like a GCC Bug. Perhaps try to find a workaround later or report as such.
//requires std::constructible_from< T, T_Args... >
std::shared_ptr< T > load( T_Args&&... args )
{
ZoneScoped;
std::lock_guard guard { queue_mtx };
T* ptr { new T( std::forward< T_Args >( args )... ) };
std::shared_ptr< T > s_ptr { ptr };
to_stage.push( s_ptr );
return s_ptr;
}
//! Returns true if all items to be staged were submitted to the queue
//! Returns false if more items remain
bool stage( vk::CommandBuffer& buffer )
{
ZoneScoped;
std::lock_guard guard { queue_mtx };
//! Number of items to process during a stage step
constexpr std::size_t max_count { 16 };
for ( std::size_t i = 0; i < max_count; ++i )
{
if ( to_stage.empty() ) break;
processing.emplace_back( to_stage.front() );
to_stage.pop();
}
if ( processing.size() == 0 ) return true;
for ( const auto& ptr : processing )
{
ptr->stage( buffer );
}
return to_stage.empty();
}
void confirmStaged()
{
for ( const auto& ptr : processing )
{
ptr->dropStaging();
ptr->setReady();
}
//TODO: Map this into a weak ptr in order to prevent duplication.
processing.clear();
}
};
} // namespace fgl::engine

View File

@@ -0,0 +1,17 @@
//
// Created by kj16609 on 6/7/24.
//
#pragma once
#include "engine/texture/Texture.hpp"
namespace fgl::engine
{
class Texture;
using TextureStore = AssetStore< Texture>;
TextureStore& getTextureStore();
} // namespace fgl::engine

View File

@@ -87,15 +87,21 @@ namespace fgl::engine
descriptor_writes.push_back( write );
}
void DescriptorSet::bindTexture( std::uint32_t binding_idx, Texture& tex )
void DescriptorSet::bindTexture( std::uint32_t binding_idx, std::shared_ptr< Texture >& tex_ptr )
{
assert( binding_idx < m_infos.size() && "Binding index out of range" );
assert(
std::holds_alternative< std::monostate >( m_infos[ binding_idx ] )
&& "Update must be called between each array bind" );
assert( tex_ptr );
//TODO: Bind temporary texture if tex_ptr is not ready.
Texture& tex { *tex_ptr };
m_infos[ binding_idx ] = tex.getImageView().descriptorInfo(
tex.getImageView().getSampler()->getVkSampler(), vk::ImageLayout::eShaderReadOnlyOptimal );
tex.getImageView().getSampler().getVkSampler(), vk::ImageLayout::eShaderReadOnlyOptimal );
vk::WriteDescriptorSet write {};
write.dstSet = m_set;

View File

@@ -61,7 +61,7 @@ namespace fgl::engine
void bindAttachment(
std::uint32_t binding_idx, ImageView& view, vk::ImageLayout layout, vk::Sampler sampler = VK_NULL_HANDLE );
void bindTexture( std::uint32_t binding_idx, Texture& tex );
void bindTexture( std::uint32_t binding_idx, std::shared_ptr< Texture >& tex_ptr );
void setName( const std::string str );
};

View File

@@ -4,49 +4,43 @@
#include "FileBrowser.hpp"
#include "engine/assets/stores.hpp"
#include "engine/filesystem/scanner/FileScanner.hpp"
#include "engine/gui/safe_include.hpp"
#include "engine/image/ImageView.hpp"
#include "engine/image/Sampler.hpp"
#include "engine/texture/Texture.hpp"
namespace fgl::engine::filesystem
{
inline static std::vector< std::unique_ptr< FileScanner > > scanners {};
inline static FileScanner* current_scanner { nullptr };
inline static std::optional< DirInfo > root {};
inline static DirInfo* current { nullptr };
inline static std::once_flag flag {};
inline static std::optional< Texture > folder_texture { std::nullopt };
inline static std::shared_ptr< Texture > folder_texture { nullptr };
inline static std::shared_ptr< Texture > file_texture { nullptr };
const std::filesystem::path path { "/home/kj16609/Desktop/Projects/cxx/Mecha/models" };
const std::filesystem::path test_path { "/home/kj16609/Desktop/Projects/cxx/Mecha/models" };
void prepareFileGUI()
{
ZoneScoped;
scanners.emplace_back( std::make_unique< FileScanner >( path ) );
//Prepare textures needed.
folder_texture = Texture::loadFromFile( "./models/folder.png" );
Sampler sampler { vk::Filter::eLinear,
vk::Filter::eLinear,
vk::SamplerMipmapMode::eLinear,
vk::SamplerAddressMode::eClampToEdge };
folder_texture->getImageView().getSampler() = std::move( sampler );
folder_texture = getTextureStore().load( "./models/folder.png", vk::Format::eR8G8B8A8Unorm );
file_texture = getTextureStore().load( "./models/file.png", vk::Format::eR8G8B8A8Unorm );
auto cmd_buffer { Device::getInstance().beginSingleTimeCommands() };
folder_texture->stage( cmd_buffer );
Device::getInstance().endSingleTimeCommands( cmd_buffer );
folder_texture->dropStaging();
root = DirInfo( test_path );
current = &root.value();
}
void FileBrowser::drawGui( FrameInfo& info )
{
ZoneScoped;
//std::call_once( flag, []() { scanners.emplace_back( std::make_unique< FileScanner >( path ) ); } );
//std::call_once( flag, []() { scanners.emplace_back( std::make_unique< FileScanner >( test_path ) ); } );
std::call_once( flag, []() { prepareFileGUI(); } );
/*
@@ -67,23 +61,24 @@ namespace fgl::engine::filesystem
}
*/
ImGui::Text( "Scanners: %ld", scanners.size() );
const auto size { ImGui::GetWindowSize() };
constexpr float desired_size { 128.0f };
const float extra { std::fmod( size.x, desired_size ) };
const auto cols { ( size.x - extra ) / desired_size };
if ( ImGui::BeginTable( "Files", cols ) )
if ( current && ImGui::BeginTable( "Files", cols ) )
{
for ( auto& scanner : scanners )
//List folders first
for ( std::size_t i = 0; i < current->folderCount(); ++i )
{
//Print out all files found at the inital depth
for ( const FileInfo& file : *scanner )
{
if ( file.depth > 1 ) continue;
if ( ImGui::TableNextColumn() ) drawFile( file );
}
ImGui::TableNextColumn();
drawFolder( current->dir( i ) );
}
for ( std::size_t i = 0; i < current->fileCount(); ++i )
{
ImGui::TableNextColumn();
drawFile( current->file( i ) );
}
ImGui::EndTable();
@@ -92,19 +87,62 @@ namespace fgl::engine::filesystem
ImGui::Columns( 1 );
}
void FileBrowser::drawFile( const FileInfo& data )
enum FileType
{
TEXTURE,
MODEL,
BINARY,
DEFAULT = BINARY,
};
FileType getFileType( const std::filesystem::path path )
{
//TODO: NEVER TRUST FILE EXTENSIONS!
const auto extension { path.extension() };
//Map
static const std::map< FileType, std::vector< std::string_view > > map {
{ TEXTURE, { ".jpg", ".png" } }, { MODEL, { ".glb", ".obj", ".gltf" } }
};
for ( const auto& [ type, extensions ] : map )
{
//Check if the file extensions matches the list for this type
if ( std::find( extensions.begin(), extensions.end(), extension ) != extensions.end() )
{
return type;
}
}
//Default
return DEFAULT;
}
void drawTexture()
{}
void drawModel()
{}
void drawBinary()
{}
void FileBrowser::drawFile( FileInfo& data )
{
ZoneScoped;
ImGui::PushID( data.path.c_str() );
ImGui::Text( data.filename.c_str() );
ImGui::Text( data.ext.c_str() );
if ( data.is_folder ) // Folders have no extension
// file_texture->drawImGui( { 128, 128 } );
file_texture->drawImGuiButton( { 128, 128 } );
if ( ImGui::BeginDragDropSource() )
{
if ( folder_texture->drawImGuiButton( { 128, 128 } ) )
{
std::cout << "Pressed thing" << std::endl;
}
ImGui::
SetDragDropPayload( "_FILE_INFO", &data, sizeof( data ), ImGuiCond_Once /* Only copy the data once */ );
ImGui::SetTooltip( data.filename.c_str() );
ImGui::EndDragDropSource();
}
ImGui::SameLine();
@@ -113,4 +151,27 @@ namespace fgl::engine::filesystem
ImGui::PopID();
}
void FileBrowser::drawFolder( DirInfo& data )
{
ZoneScoped;
ImGui::PushID( data.path.c_str() );
ImGui::Text( data.path.filename().c_str() );
ImGui::Text( "Folder" );
if ( folder_texture->drawImGuiButton( { 128, 128 } ) )
{
openFolder( data );
}
ImGui::PopID();
}
void FileBrowser::openFolder( DirInfo& dir )
{
if ( !current ) throw std::runtime_error( "No current folder?" );
current = &dir;
}
} // namespace fgl::engine::filesystem

View File

@@ -18,12 +18,15 @@ namespace fgl::engine::filesystem
{
static FileBrowser& getInstance();
static void goUp();
static void addFolderToRoot();
static void openFolderRoot( const std::string str );
static void openFolder( DirInfo& dir );
static void drawGui( FrameInfo& info );
static void drawFile( const FileInfo& data );
static void drawFile( FileInfo& data );
static void drawFolder( DirInfo& data );
};
} // namespace fgl::engine::filesystem

View File

@@ -8,144 +8,59 @@
#include "FileScanner.hpp"
#include <queue>
#include "engine/logging/logging.hpp"
namespace fgl::engine::filesystem
{
FileInfo& FileScannerGenerator::operator()()
DirInfo::DirInfo( const std::filesystem::path& dir, DirInfo* dir_parent ) :
path( dir ),
total_size( 0 ),
parent( dir_parent )
{
if ( m_h.done() ) throw std::runtime_error( "FileScannerGenerator is done but operator was still called" );
m_h();
if ( m_h.promise().exception ) std::rethrow_exception( m_h.promise().exception );
if ( m_h.promise().value.has_value() )
for ( auto itter = std::filesystem::directory_iterator( dir ); itter != std::filesystem::directory_iterator();
++itter )
{
return m_h.promise().value.value();
if ( itter->is_regular_file() )
{
files.emplace_back( *itter );
}
else if ( itter->is_directory() )
{
nested_dirs_to_scan.push( *itter );
}
else
throw std::runtime_error( "Unknown/Unspported file type" );
}
else
throw std::runtime_error( "Failed to get value from FileScannerGenerator." );
}
FileScannerGenerator scan_files( const std::filesystem::path path )
std::size_t DirInfo::fileCount() const
{
if ( !std::filesystem::exists( path ) )
return files.size();
}
FileInfo& DirInfo::file( const std::size_t index )
{
return files[ index ];
}
DirInfo& DirInfo::dir( const std::size_t index )
{
if ( index >= nested_dirs.size() + nested_dirs_to_scan.size() ) throw std::runtime_error( "Index OOB" );
if ( index >= nested_dirs.size() )
{
log::error( "Expected path does not exist: {}", path.string() );
throw std::runtime_error( format_ns::format( "Path {} does not exist.", path ).c_str() );
}
auto dir_empty = []( const std::filesystem::path& dir_path ) -> bool
{ return std::filesystem::directory_iterator( dir_path ) == std::filesystem::directory_iterator(); };
if ( dir_empty( path ) ) co_return FileInfo { path, path, 0, 0 };
std::queue< std::pair< std::filesystem::path, std::uint8_t > > dirs {};
dirs.push( { path, 0 } );
while ( dirs.size() > 0 )
{
const auto [ dir, depth ] { std::move( dirs.front() ) };
dirs.pop();
std::vector< std::filesystem::path > nested_dirs {};
log::debug( "Searching {}", dir );
//Recurse through the directory.
for ( auto itter = std::filesystem::directory_iterator( dir );
itter != std::filesystem::directory_iterator(); )
while ( nested_dirs.size() <= index )
{
log::debug( "Found: {}", dir );
if ( itter->is_directory() )
{
//Add directory to scan list.
nested_dirs.emplace_back( *itter );
++itter;
continue;
}
auto to_scan { std::move( nested_dirs_to_scan.front() ) };
nested_dirs_to_scan.pop();
log::debug( "Processed folder: {}", to_scan );
FileInfo info {
*itter, path, itter->is_regular_file() ? itter->file_size() : 0, std::uint8_t( depth + 1 )
};
++itter;
//If we are at the last file and there are no more directories to scan then return.
if ( itter == std::filesystem::directory_iterator() && dirs.size() == 0 && nested_dirs.size() == 0 )
{
log::debug( "co_return: {}", info.path );
co_return std::move( info );
}
else
{
log::debug( "co_yield: {}", info.path );
co_yield std::move( info );
}
}
//Add the nested dirs to the scanlist and yield them
for ( std::size_t i = 0; i < nested_dirs.size(); ++i )
{
FileInfo info { nested_dirs.at( i ), path, 0, std::uint8_t( depth + 1 ) };
// Check if the directory is empty and if it's not then add it to the scan queue.
const bool is_empty { dir_empty( nested_dirs.at( i ) ) };
//If the dir is empty, if we are done searching our current nested list, and there are no more dirs to process, Return
if ( is_empty && i == nested_dirs.size() - 1 && dirs.size() == 0 )
{
log::debug( "co_return: {}", info.path );
co_return std::move( info );
}
if ( !is_empty )
{
dirs.push( { nested_dirs.at( i ), depth + 1 } );
}
co_yield std::move( info );
nested_dirs.emplace_back( to_scan, this );
}
}
spdlog::critical( "Got to an illegal spot!" );
std::unreachable();
return nested_dirs[ index ];
}
FileScanner::FileScanner( const std::filesystem::path& path ) : m_path( path ), file_scanner( scan_files( path ) )
{
files.emplace_back( std::move( file_scanner() ) );
}
const FileInfo& FileScanner::at( std::size_t index )
{
if ( index >= files.size() && !file_scanner.m_h.done() )
{
// Index is higher then what we have.
// Scanner is also NOT done.
// We use the coroutine to fetch what the next file should be.
std::size_t diff { index - ( files.size() - 1 ) };
while ( diff > 0 && !file_scanner.m_h.done() )
{
files.emplace_back( std::move( file_scanner() ) );
--diff;
}
}
if ( index >= files.size() )
throw std::
runtime_error( format_ns::format( "index = {}: size < index : {} < {}", index, files.size(), index )
.c_str() );
return files.at( index );
}
bool FileScanner::iterator::operator==( const std::unreachable_sentinel_t ) const
{
return m_scanner.file_scanner.m_h.done() && ( m_idx == m_scanner.files.size() );
}
} // namespace fgl::engine::filesystem

View File

@@ -3,19 +3,47 @@
//
#pragma once
#ifndef ATLASGAMEMANAGER_FILESCANNER_HPP
#define ATLASGAMEMANAGER_FILESCANNER_HPP
#include <coroutine>
#include <exception>
#include <filesystem>
#include <queue>
#include <string>
#include <vector>
#include "engine/logging/logging.hpp"
namespace fgl::engine::filesystem
{
struct FileInfo;
struct DirInfo
{
std::filesystem::path path;
std::size_t total_size;
std::vector< FileInfo > files {};
std::vector< DirInfo > nested_dirs {};
std::queue< std::filesystem::path > nested_dirs_to_scan {};
DirInfo* parent { nullptr };
public:
inline DirInfo* up() const { return parent; }
std::size_t fileCount() const;
FileInfo& file( const std::size_t index );
inline std::size_t folderCount() const { return nested_dirs.size() + nested_dirs_to_scan.size(); }
DirInfo& dir( const std::size_t index );
DirInfo() = delete;
DirInfo( const std::filesystem::path& path, DirInfo* parent = nullptr );
DirInfo( const DirInfo& other ) = delete;
DirInfo& operator=( const DirInfo& other ) = delete;
DirInfo( DirInfo&& ) = default;
DirInfo& operator=( DirInfo&& ) = default;
};
struct FileInfo
{
@@ -23,23 +51,15 @@ namespace fgl::engine::filesystem
std::string ext;
std::filesystem::path path;
std::size_t size;
std::uint8_t depth;
std::filesystem::path relative;
bool is_folder;
FileInfo() = delete;
FileInfo(
std::filesystem::path path_in,
const std::filesystem::path& source,
const std::size_t filesize,
const std::uint8_t file_depth ) :
FileInfo( std::filesystem::path path_in ) :
filename( path_in.filename().string() ),
ext( path_in.extension().string() ),
path( path_in ),
size( filesize ),
depth( file_depth ),
relative( std::filesystem::relative( std::move( path_in ), source ) ),
size( std::filesystem::file_size( path_in ) ),
is_folder( std::filesystem::is_directory( path ) )
{}
@@ -50,117 +70,4 @@ namespace fgl::engine::filesystem
FileInfo& operator=( FileInfo&& other ) = default;
};
struct FileScannerGenerator
{
struct promise_type;
using handle_type = std::coroutine_handle< promise_type >;
struct promise_type
{
std::optional< FileInfo > value { std::nullopt };
std::exception_ptr exception { nullptr };
FileScannerGenerator get_return_object()
{
return FileScannerGenerator( handle_type::from_promise( *this ) );
}
std::suspend_always initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception()
{
exception = std::current_exception();
log::critical( "Exception thrown in FileScanner" );
}
void return_value( FileInfo&& from )
{
if ( from.filename == "" )
throw std::runtime_error( "FileScannerGenerator: return value had no filename!" );
value = std::move( from );
}
std::suspend_always yield_value( FileInfo&& from )
{
if ( from.filename == "" )
throw std::runtime_error( "FromScannerGenerator:: yield value had no filename!" );
value = std::forward< FileInfo >( from );
return {};
}
};
handle_type m_h;
FileScannerGenerator() = delete;
FileScannerGenerator( handle_type h ) : m_h( h ) {}
~FileScannerGenerator()
{
log::debug( "Destroying Generator" );
m_h.destroy();
}
FileInfo& operator()();
};
class FileScanner
{
private:
std::filesystem::path m_path;
FileScannerGenerator file_scanner;
std::vector< FileInfo > files {};
const FileInfo& at( std::size_t index );
friend class iterator;
class iterator
{
std::size_t m_idx { 0 };
FileScanner& m_scanner;
public:
iterator() = delete;
iterator( const std::size_t idx, FileScanner& scanner ) : m_idx( idx ), m_scanner( scanner ) {}
FileScanner::iterator& operator++()
{
++m_idx;
return *this;
}
// Operator != required to check for end I assume. Where if the this returns true then we are good to continue
// So instead we can just return the state of the scanner. And if the scanner is complete then we'll return false here.
//bool operator !=
bool operator==( const std::unreachable_sentinel_t ) const;
// Required for the for loop
const FileInfo& operator*() { return m_scanner.at( m_idx ); }
};
public:
FileScanner() = delete;
FileScanner( const std::filesystem::path& path );
FileScanner( const std::string_view path ) : FileScanner( std::filesystem::path( path ) ) {}
FileScanner( const FileScanner& other ) = delete; //Copy
FileScanner& operator=( const FileScanner& other ) = delete; //Copy-Assign
iterator begin() { return iterator( 0, *this ); }
//This *probably* isn't required(?) but the for loop will want it anyways. So we can just return literaly anything here since it's not used anyways.
std::unreachable_sentinel_t end() { return {}; }
std::filesystem::path path() const { return m_path; }
};
} // namespace fgl::engine::filesystem
#endif //ATLASGAMEMANAGER_FILESCANNER_HPP

View File

@@ -7,7 +7,7 @@
namespace fgl::engine
{
ImageView::ImageView( std::shared_ptr< ImageHandle >& img ) : m_resource( img )
ImageView::ImageView( std::shared_ptr< ImageHandle >& img ) : m_resource( img ), m_sampler()
{
vk::ImageViewCreateInfo view_info {};
view_info.image = img->getVkImage();

View File

@@ -21,7 +21,7 @@ namespace fgl::engine
{
std::shared_ptr< ImageHandle > m_resource;
std::optional< Sampler > m_sampler { std::nullopt };
Sampler m_sampler;
vk::DescriptorImageInfo m_descriptor_info {};
@@ -38,7 +38,12 @@ namespace fgl::engine
ImageView( ImageView&& other ) noexcept :
m_resource( std::move( other.m_resource ) ),
m_descriptor_info( std::move( other.m_descriptor_info ) ),
m_image_view( std::move( other.m_image_view ) )
m_image_view( std::move( other.m_image_view ) ),
m_sampler(
vk::Filter::eLinear,
vk::Filter::eLinear,
vk::SamplerMipmapMode::eLinear,
vk::SamplerAddressMode::eClampToEdge )
{
other.m_image_view = VK_NULL_HANDLE;
}
@@ -59,7 +64,7 @@ namespace fgl::engine
vk::Image& getVkImage();
std::optional< Sampler >& getSampler() { return m_sampler; };
Sampler& getSampler() { return m_sampler; };
vk::DescriptorImageInfo descriptorInfo( vk::Sampler sampler, vk::ImageLayout layout ) const;
vk::DescriptorImageInfo descriptorInfo( vk::ImageLayout layout ) const;

View File

@@ -16,7 +16,13 @@ namespace fgl::engine
public:
Sampler() = delete;
Sampler() :
Sampler(
vk::Filter::eLinear,
vk::Filter::eLinear,
vk::SamplerMipmapMode::eLinear,
vk::SamplerAddressMode::eClampToBorder )
{}
Sampler(
vk::Filter min_filter,

View File

@@ -133,18 +133,13 @@ namespace fgl::engine
return model_ptr;
}
void Model::syncBuffers( vk::CommandBuffer& cmd_buffer )
void Model::stage( vk::CommandBuffer& cmd_buffer )
{
assert( !m_primitives.empty() );
for ( auto& primitive : m_primitives )
{
primitive.m_vertex_buffer.stage( cmd_buffer );
primitive.m_index_buffer.stage( cmd_buffer );
if ( primitive.m_texture.has_value() )
{
primitive.m_texture->stage( cmd_buffer );
}
}
}

View File

@@ -64,7 +64,7 @@ namespace fgl::engine
static std::vector< std::shared_ptr< Model > > createModelsFromScene(
Device& device, const std::filesystem::path& path, Buffer& vertex_buffer, Buffer& index_buffer );
void syncBuffers( vk::CommandBuffer& cmd_buffer );
void stage( vk::CommandBuffer& cmd_buffer );
const std::string& getName() const { return m_name; }

View File

@@ -42,7 +42,7 @@ namespace fgl::engine
OrientedBoundingBox< CoordinateSpace::Model > m_bounding_box;
PrimitiveMode m_mode;
std::optional< Texture > m_texture { std::nullopt };
std::shared_ptr< Texture > m_texture;
Primitive(
VertexBufferSuballocation&& vertex_buffer,
@@ -59,13 +59,13 @@ namespace fgl::engine
VertexBufferSuballocation&& vertex_buffer,
IndexBufferSuballocation&& index_buffer,
const OrientedBoundingBox< CoordinateSpace::Model >& bounding_box,
Texture&& texture,
std::shared_ptr< Texture >&& texture,
const PrimitiveMode mode ) :
m_vertex_buffer( std::move( vertex_buffer ) ),
m_index_buffer( std::move( index_buffer ) ),
m_bounding_box( bounding_box ),
m_mode( mode ),
m_texture( std::move( texture ) )
m_texture( std::forward< decltype( m_texture ) >( texture ) )
{}
Primitive() = delete;

View File

@@ -131,7 +131,7 @@ namespace fgl::engine
return root.accessors.at( prim.attributes.at( attrib ) );
}
Texture SceneBuilder::loadTexture( const tinygltf::Primitive& prim, const tinygltf::Model& root )
std::shared_ptr< Texture > SceneBuilder::loadTexture( const tinygltf::Primitive& prim, const tinygltf::Model& root )
{
ZoneScoped;
const auto mat_idx { prim.material };

View File

@@ -59,7 +59,7 @@ namespace fgl::engine
const tinygltf::Accessor& getAccessorForAttribute(
const tinygltf::Primitive& prim, const tinygltf::Model& root, const std::string attrib ) const;
Texture loadTexture( const tinygltf::Primitive& prim, const tinygltf::Model& root );
std::shared_ptr< Texture > loadTexture( const tinygltf::Primitive& prim, const tinygltf::Model& root );
public:

View File

@@ -9,6 +9,7 @@
#pragma GCC diagnostic pop
#include "ModelBuilder.hpp"
#include "engine/assets/stores.hpp"
#include "engine/descriptors/DescriptorSet.hpp"
#include "engine/image/ImageView.hpp"
#include "engine/image/Sampler.hpp"
@@ -253,14 +254,18 @@ namespace fgl::engine
sampler.wrapS == sampler.wrapT
&& "Can't support different wrap modes for textures on each axis" );
Texture tex { Texture::loadFromFile( filepath.parent_path() / source.uri ) };
//TOOD: Get format from texture info and convert to vkFOrmat
std::shared_ptr< Texture > tex {
getTextureStore().load( filepath.parent_path() / source.uri, vk::Format::eR8G8B8A8Unorm )
};
Sampler smp { translateFilterToVK( sampler.minFilter ),
translateFilterToVK( sampler.magFilter ),
vk::SamplerMipmapMode::eLinear,
translateWarppingToVk( sampler.wrapS ) };
tex.getImageView().getSampler() = std::move( smp );
tex.createImGuiSet();
tex->getImageView().getSampler() = std::move( smp );
tex->createImGuiSet();
Texture::getTextureDescriptorSet().bindTexture( 0, tex );
Texture::getTextureDescriptorSet().update();
@@ -268,10 +273,6 @@ namespace fgl::engine
//Stage texture
auto cmd { Device::getInstance().beginSingleTimeCommands() };
tex.stage( cmd );
Device::getInstance().endSingleTimeCommands( cmd );
tex.dropStaging();
Primitive prim { std::move( vertex_buffer ),
std::move( index_buffer ),
bounding_box,

View File

@@ -46,12 +46,12 @@ namespace fgl::engine
// If the textureless flag is on and we have a texture then skip the primitive.c
if ( options & TEXTURELESS )
{
if ( primitive.m_texture.has_value() ) continue;
if ( primitive.m_texture ) continue;
}
else
{
// Flag is not present
if ( !primitive.m_texture.has_value() ) continue;
if ( !primitive.m_texture ) continue;
}
const auto key { std::make_pair( matrix_info.texture_idx, primitive.m_index_buffer.getOffset() ) };

View File

@@ -5,7 +5,7 @@
#pragma once
#include "engine/GameObject.hpp"
#include "engine/primitives/Frustum.hpp"
#include "engine/texture/TextureHandle.hpp"
#include "engine/texture/Texture.hpp"
#include "engine/utils.hpp"
namespace fgl::engine

View File

@@ -6,10 +6,8 @@
#include <initializer_list>
#include "TextureHandle.hpp"
#include "engine/FrameInfo.hpp"
#include "engine/buffers/BufferSuballocation.hpp"
#include "engine/descriptors/DescriptorPool.hpp"
#include "engine/descriptors/DescriptorSet.hpp"
#include "engine/image/ImageView.hpp"
#include "objectloaders/stb_image.h"
@@ -27,9 +25,8 @@
namespace fgl::engine
{
inline static std::unordered_map< std::string, std::weak_ptr< TextureHandle > > texture_map;
std::tuple< std::vector< std::byte >, int, int, int > loadTexture( const std::filesystem::path& path )
std::tuple< std::vector< std::byte >, int, int, vk::Format >
loadTexture( const std::filesystem::path& path, const vk::Format format )
{
ZoneScoped;
if ( !std::filesystem::exists( path ) ) throw std::runtime_error( "Failed to open file: " + path.string() );
@@ -49,42 +46,14 @@ namespace fgl::engine
stbi_image_free( data_c );
return { std::move( data ), x, y, 4 };
}
//TODO: Write check to ensure the format matches the number of channels
Texture Texture::loadFromFile( const std::filesystem::path& path )
{
ZoneScoped;
log::debug( "Loading texture: {}", path );
//TODO: Make some way of cleaning the map when loading textures
if ( texture_map.contains( path.string() ) )
{
if ( auto itter = texture_map.find( path.string() ); !itter->second.expired() )
{
return Texture( itter->second.lock() );
}
else
{
//Texture is expired. So it'll need to be reloaded.
texture_map.erase( itter );
}
}
const auto data { loadTexture( path ) };
Texture tex { data };
tex.m_handle->m_image_view->setName( path.string() );
texture_map.emplace( path.string(), tex.m_handle );
log::debug( "Loaded texture at {} with res {}x{}", path, tex.getExtent().width, tex.getExtent().height );
return std::move( tex );
return { std::move( data ), x, y, format };
}
void Texture::drawImGui( vk::Extent2D extent )
{
if ( this->m_handle->m_imgui_set == VK_NULL_HANDLE ) createImGuiSet();
if ( this->m_imgui_set == VK_NULL_HANDLE ) createImGuiSet();
if ( extent == vk::Extent2D() )
{
@@ -98,7 +67,7 @@ namespace fgl::engine
bool Texture::drawImGuiButton( vk::Extent2D extent )
{
if ( this->m_handle->m_imgui_set == VK_NULL_HANDLE ) createImGuiSet();
if ( this->m_imgui_set == VK_NULL_HANDLE ) createImGuiSet();
if ( extent == vk::Extent2D() )
{
@@ -107,44 +76,63 @@ namespace fgl::engine
const ImVec2 imgui_size { static_cast< float >( extent.width ), static_cast< float >( extent.height ) };
if ( !isReady() )
{
//TODO: Render placeholder
log::warn( "Attempted to render texture {} but texture was not ready!", this->m_texture_id );
return ImGui::Button( "No texture :(" );
}
return ImGui::ImageButton( static_cast< ImTextureID >( getImGuiDescriptorSet() ), imgui_size );
}
Texture Texture::generateFromPerlinNoise( int x_size, int y_size, std::size_t seed )
{
ZoneScoped;
const std::vector< std::byte > data { generatePerlinImage( { x_size, y_size }, 15, seed ) };
Texture tex { data, x_size, y_size, 4 };
return std::move( tex );
}
Texture::Texture( std::shared_ptr< TextureHandle > handle ) : m_handle( std::move( handle ) )
{}
Texture::Texture( const std::tuple< std::vector< std::byte >, int, int, int >& tuple ) :
Texture::Texture( const std::tuple< std::vector< std::byte >, int, int, vk::Format >& tuple ) :
Texture( std::get< 0 >( tuple ), std::get< 1 >( tuple ), std::get< 2 >( tuple ), std::get< 3 >( tuple ) )
{}
Texture::Texture( const std::vector< std::byte >& data, const int x, const int y, const int channels ) :
Texture( data, vk::Extent2D( x, y ), channels )
Texture::Texture( const std::vector< std::byte >& data, const int x, const int y, const vk::Format format ) :
Texture( data, vk::Extent2D( x, y ), format )
{}
Texture::Texture( const std::vector< std::byte >& data, const vk::Extent2D extent, const int channels ) :
m_handle( std::make_shared< TextureHandle >( data, extent, channels ) )
Texture::Texture( const std::vector< std::byte >& data, const vk::Extent2D extent, const vk::Format format ) :
m_extent( extent )
{
ZoneScoped;
static TextureID tex_counter { 0 };
auto image = std::make_shared< Image >(
extent,
format,
vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled,
vk::ImageLayout::eUndefined,
vk::ImageLayout::eShaderReadOnlyOptimal );
m_image_view = image->getView();
m_texture_id = tex_counter++;
m_staging = std::make_unique< BufferSuballocation >( getGlobalStagingBuffer(), data.size() );
//Copy data info buffer
std::memcpy( reinterpret_cast< unsigned char* >( m_staging->ptr() ), data.data(), data.size() );
}
Texture::Texture( const std::filesystem::path& path, const vk::Format format ) :
Texture( loadTexture( path, format ) )
{}
Texture::~Texture()
{
if ( m_imgui_set != VK_NULL_HANDLE ) ImGui_ImplVulkan_RemoveTexture( m_imgui_set );
}
void Texture::stage( vk::CommandBuffer& cmd )
{
ZoneScoped;
assert( m_handle && "Attempted to stage invalid texture (No handle)" );
//assert( m_handle->m_staging && "Can't stage. No staging buffer made" );
//assert( m_staging && "Can't stage. No staging buffer made" );
//TODO: I need some way of telling if a Texture HAS been staged rather then simply checking if the staging buffer is present
// Since just checking if the buffer is present could not mean it has been staged (IE staged but not dropped, Or never created in the first place)
if ( !m_handle->m_staging ) return;
// Texutres are made with a staging buffer in RAM, Thus if the buffer has been dropped then we have been sucesfully staged.
if ( !m_staging ) return;
vk::ImageSubresourceRange range;
range.aspectMask = vk::ImageAspectFlagBits::eColor;
@@ -156,7 +144,7 @@ namespace fgl::engine
vk::ImageMemoryBarrier barrier {};
barrier.oldLayout = vk::ImageLayout::eUndefined;
barrier.newLayout = vk::ImageLayout::eTransferDstOptimal;
barrier.image = m_handle->m_image_view->getVkImage();
barrier.image = m_image_view->getVkImage();
barrier.subresourceRange = range;
barrier.srcAccessMask = {};
barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite;
@@ -172,7 +160,7 @@ namespace fgl::engine
barriers_to );
vk::BufferImageCopy region {};
region.bufferOffset = m_handle->m_staging->getOffset();
region.bufferOffset = m_staging->getOffset();
region.bufferRowLength = 0;
region.bufferImageHeight = 0;
@@ -182,21 +170,17 @@ namespace fgl::engine
region.imageSubresource.layerCount = 1;
region.imageOffset = vk::Offset3D( 0, 0, 0 );
region.imageExtent = vk::Extent3D( m_handle->m_extent, 1 );
region.imageExtent = vk::Extent3D( m_extent, 1 );
cmd.copyBufferToImage(
m_handle->m_staging->getVkBuffer(),
m_handle->m_image_view->getVkImage(),
vk::ImageLayout::eTransferDstOptimal,
1,
&region );
m_staging->getVkBuffer(), m_image_view->getVkImage(), vk::ImageLayout::eTransferDstOptimal, 1, &region );
//Transfer back to eGeneral
vk::ImageMemoryBarrier barrier_from {};
barrier_from.oldLayout = barrier.newLayout;
barrier_from.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
barrier_from.image = m_handle->m_image_view->getVkImage();
barrier_from.image = m_image_view->getVkImage();
barrier_from.subresourceRange = range;
barrier_from.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
barrier_from.dstAccessMask = vk::AccessFlagBits::eShaderRead;
@@ -214,57 +198,55 @@ namespace fgl::engine
void Texture::dropStaging()
{
m_handle->m_staging.reset();
assert( m_staging );
m_staging.reset();
}
vk::DescriptorImageInfo Texture::getDescriptor() const
{
return m_handle->m_image_view->descriptorInfo( vk::ImageLayout::eGeneral );
return m_image_view->descriptorInfo( vk::ImageLayout::eGeneral );
}
vk::Extent2D Texture::getExtent() const
{
return m_handle->m_image_view->getExtent();
return m_image_view->getExtent();
}
ImageView& Texture::getImageView()
{
assert( m_handle );
assert( m_handle->m_image_view );
return *m_handle->m_image_view;
assert( m_image_view );
return *m_image_view;
}
void Texture::createImGuiSet()
{
#if ENABLE_IMGUI
if ( m_handle->m_imgui_set != VK_NULL_HANDLE ) return;
if ( m_imgui_set != VK_NULL_HANDLE ) return;
auto& view { m_handle->m_image_view };
auto& view { m_image_view };
assert( view );
VkImageView vk_view { view->getVkView() };
assert( vk_view );
assert( view->getSampler() );
VkSampler vk_sampler { view->getSampler()->getVkSampler() };
VkSampler vk_sampler { view->getSampler().getVkSampler() };
m_handle->m_imgui_set =
ImGui_ImplVulkan_AddTexture( vk_sampler, vk_view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL );
m_imgui_set = ImGui_ImplVulkan_AddTexture( vk_sampler, vk_view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL );
#endif
}
vk::DescriptorSet& Texture::getImGuiDescriptorSet()
{
assert( m_handle );
assert( m_handle->m_imgui_set != VK_NULL_HANDLE );
return m_handle->m_imgui_set;
assert( !m_staging );
assert( m_imgui_set != VK_NULL_HANDLE );
return m_imgui_set;
}
TextureID Texture::getID() const
{
assert( m_handle );
return m_handle->m_texture_id;
assert( !m_staging );
return m_texture_id;
}
DescriptorSet& Texture::getTextureDescriptorSet()

View File

@@ -8,36 +8,62 @@
#include <filesystem>
#include "TextureHandle.hpp"
#include "engine/assets/AssetManager.hpp"
namespace fgl::engine
{
class BufferSuballocation;
class ImageView;
class DescriptorSet;
class TextureHandle;
//TODO: Implement texture handle map to avoid loading the same texture multiple times
class Texture
{
std::shared_ptr< TextureHandle > m_handle;
//! Has this texture been submitted to the GPU?
bool submitte_to_gpu { false };
using TextureID = std::uint32_t;
[[nodiscard]] Texture( const std::tuple< std::vector< std::byte >, int, int, int >& );
[[nodiscard]] Texture( std::shared_ptr< TextureHandle > handle );
class Texture;
using TextureStore = AssetStore< Texture >;
//TODO: Implement texture handle map to avoid loading the same texture multiple times
class Texture final : public AssetInterface< Texture >, public std::enable_shared_from_this< Texture >
{
template < typename T >
friend class AssetStore;
//TODO: Implement reusing texture ids
TextureID m_texture_id { std::numeric_limits< TextureID >::infinity() };
std::shared_ptr< ImageView > m_image_view {};
std::unique_ptr< BufferSuballocation > m_staging { nullptr };
vk::Extent2D m_extent { 0, 0 };
vk::DescriptorSet m_imgui_set { VK_NULL_HANDLE };
[[nodiscard]] Texture( const std::tuple< std::vector< std::byte >, int, int, vk::Format >& );
[[nodiscard]]
Texture( const std::vector< std::byte >& data, const int x, const int y, const vk::Format texture_format );
[[nodiscard]]
Texture( const std::vector< std::byte >& data, const vk::Extent2D extent, const vk::Format texture_format );
[[nodiscard]] Texture( const std::filesystem::path& path, const vk::Format format );
void stage( vk::CommandBuffer& cmd ) override;
void dropStaging();
public:
[[nodiscard]] Texture( const std::vector< std::byte >& data, const int x, const int y, const int channels );
[[nodiscard]] Texture( const std::vector< std::byte >& data, const vk::Extent2D extent, const int channels );
Texture() = delete;
Texture( const Texture& ) = default;
~Texture();
Texture( const Texture& ) = delete;
Texture& operator=( const Texture& ) = delete;
Texture( Texture&& other ) = default;
Texture& operator=( Texture&& ) = default;
void stage( vk::CommandBuffer& cmd );
void dropStaging();
Texture( Texture&& other ) = delete;
Texture& operator=( Texture&& ) = delete;
[[nodiscard]] TextureID getID() const;
@@ -50,11 +76,8 @@ namespace fgl::engine
void createImGuiSet();
[[nodiscard]] static Texture generateFromPerlinNoise( int x_size, int y_size, std::size_t seed = 0 );
[[nodiscard]] static Texture loadFromFile( const std::filesystem::path& path );
void drawImGui( vk::Extent2D extent = {} );
bool drawImGuiButton(vk::Extent2D extent = {});
bool drawImGuiButton( vk::Extent2D extent = {} );
static DescriptorSet& getTextureDescriptorSet();
};

View File

@@ -1,51 +0,0 @@
//
// Created by kj16609 on 1/22/24.
//
#include "TextureHandle.hpp"
#include "engine/buffers/BufferSuballocation.hpp"
#include "engine/image/Image.hpp"
#include "engine/image/ImageView.hpp"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Weffc++"
#pragma GCC diagnostic ignored "-Wold-style-cast"
#pragma GCC diagnostic ignored "-Wconversion"
#include "imgui/backends/imgui_impl_vulkan.h"
#pragma GCC diagnostic pop
namespace fgl::engine
{
TextureHandle::TextureHandle(
const std::vector< std::byte >& data, const vk::Extent2D extent, [[maybe_unused]] const int channels ) :
m_extent( extent )
{
ZoneScoped;
static TextureID tex_counter { 0 };
constexpr auto format { vk::Format::eR8G8B8A8Unorm };
auto image = std::make_shared< Image >(
extent,
format,
vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled,
vk::ImageLayout::eUndefined,
vk::ImageLayout::eShaderReadOnlyOptimal );
m_image_view = image->getView();
m_texture_id = tex_counter++;
m_staging = std::make_unique< BufferSuballocation >( getGlobalStagingBuffer(), data.size() );
//Copy data info buffer
std::memcpy( reinterpret_cast< unsigned char* >( m_staging->ptr() ), data.data(), data.size() );
}
TextureHandle::~TextureHandle()
{
if ( m_imgui_set != VK_NULL_HANDLE ) ImGui_ImplVulkan_RemoveTexture( m_imgui_set );
}
} // namespace fgl::engine

View File

@@ -1,40 +0,0 @@
//
// Created by kj16609 on 1/22/24.
//
#pragma once
#include <vulkan/vulkan.hpp>
#include <cstdint>
#include <memory>
namespace fgl::engine
{
class ImageView;
class BufferSuballocation;
using TextureID = std::uint32_t;
class TextureHandle
{
//TODO: Implement reusing texture ids
TextureID m_texture_id { std::numeric_limits< TextureID >::infinity() };
std::shared_ptr< ImageView > m_image_view {};
std::unique_ptr< BufferSuballocation > m_staging { nullptr };
vk::Extent2D m_extent { 0, 0 };
vk::DescriptorSet m_imgui_set { VK_NULL_HANDLE };
friend class Texture;
public:
TextureHandle( const std::vector< std::byte >& data, const vk::Extent2D extent, const int channels );
~TextureHandle();
};
} // namespace fgl::engine