headless window + renderering (or attempts to)
This commit is contained in:
parent
df4e9dcb36
commit
bd416423b3
8
Makefile
8
Makefile
@ -66,6 +66,11 @@ OPTIMIZATIONS = -O3 -fstrict-aliasing -DUF_NO_EXCEPTIONS
|
||||
WARNINGS = -Wall -Wno-unknown-pragmas -Wno-unused-function -Wno-unused-variable -Wno-switch -Wno-reorder -Wno-sign-compare -Wno-unused-but-set-variable -Wno-ignored-attributes -Wno-narrowing -Wno-misleading-indentation
|
||||
FLAGS += -std=c++2b $(OPTIMIZATIONS) $(WARNINGS) -fdiagnostics-color=always
|
||||
|
||||
# Headless variant
|
||||
ifeq ($(HEADLESS),1)
|
||||
FLAGS += -DUF_HEADLESS
|
||||
endif
|
||||
|
||||
# Base Library Definitions
|
||||
LIB_NAME += uf
|
||||
EXT_LIB_NAME += ext
|
||||
@ -135,7 +140,8 @@ $(PREFIX): $(EX_DLL) $(EXT_EX_DLL) $(TARGET) $(TARGET_SHADERS)
|
||||
$(CC) $(FLAGS) $(INCS) -c $< -o $@
|
||||
|
||||
ifneq ($(ARCH),dreamcast)
|
||||
$(TARGET): $(OBJS)
|
||||
|
||||
$(TARGET): $(OBJS) $(EX_DLL) $(EXT_EX_DLL)
|
||||
$(CXX) $(FLAGS) $(OBJS) $(LIBS) $(INCS) $(LINKS) -l$(LIB_NAME) -l$(EXT_LIB_NAME) -o $(TARGET)
|
||||
endif
|
||||
|
||||
|
||||
@ -10,8 +10,33 @@
|
||||
#include <uf/utils/thread/thread.h>
|
||||
#include <uf/utils/renderer/renderer.h>
|
||||
#include <uf/utils/window/payloads.h>
|
||||
#include <uf/utils/io/console.h>
|
||||
|
||||
#include <uf/ext/openvr/openvr.h> // yuck
|
||||
#if UF_USE_LUA
|
||||
#include <uf/ext/lua/lua.h>
|
||||
#endif
|
||||
|
||||
#if UF_ENV_HEADLESS
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <iostream>
|
||||
|
||||
namespace {
|
||||
// stdin command channel: a detached thread reads lines into a queue that
|
||||
// the main thread drains in client::tick via the console
|
||||
struct HeadlessIO {
|
||||
std::atomic<bool> running = false;
|
||||
bool eof = false;
|
||||
bool eofAnnounced = false;
|
||||
std::mutex mutex;
|
||||
uf::stl::vector<uf::stl::string> queue;
|
||||
};
|
||||
// intentionally leaked so the reader thread never touches freed memory
|
||||
HeadlessIO* headlessIO = nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool client::ready = false;
|
||||
bool client::terminated = false;
|
||||
@ -28,25 +53,38 @@ void client::initialize() {
|
||||
/* Initialize window */ {
|
||||
// Window size
|
||||
pod::Vector2i size = uf::vector::decode( client::config["window"]["size"], pod::Vector2i{} );
|
||||
#if UF_ENV_HEADLESS
|
||||
// no display to query; fall back to a fixed size
|
||||
if ( size.x <= 0 && size.y <= 0 ) size = { 1280, 720 };
|
||||
// fake a refresh rate so the auto frame limiter still works
|
||||
uf::config["window"]["refresh rate"] = 60;
|
||||
// servers usually have no audio device; keep the engine quiet
|
||||
uf::config["engine"]["audio"]["mute"] = true;
|
||||
// missing assets should log, not kill a headless session
|
||||
uf::config["engine"]["debug"]["loader"]["assert"] = false;
|
||||
#else
|
||||
// request system size
|
||||
if ( size.x <= 0 && size.y <= 0 ) {
|
||||
auto resolution = client::window.getResolution();
|
||||
client::config["window"]["size"][0] = (size.x = resolution.x);
|
||||
client::config["window"]["size"][1] = (size.y = resolution.y);
|
||||
}
|
||||
#endif
|
||||
// Window title
|
||||
uf::stl::string title; {
|
||||
title = client::config["window"]["title"].as<std::string>();
|
||||
}
|
||||
#if !UF_ENV_HEADLESS
|
||||
// Terminal window;
|
||||
spec::terminal.setVisible( client::config["window"]["terminal"]["visible"].as<bool>() );
|
||||
#endif
|
||||
// Ncurses
|
||||
uf::IoStream::ncurses = client::config["window"]["terminal"]["ncurses"].as<bool>();
|
||||
// Window's context settings
|
||||
uf::renderer::settings::width = size.x;
|
||||
uf::renderer::settings::height = size.y;
|
||||
client::window.create( size, title );
|
||||
#if !UF_ENV_DREAMCAST
|
||||
#if !UF_ENV_DREAMCAST && !UF_ENV_HEADLESS
|
||||
// Set refresh rate
|
||||
uf::config["window"]["refresh rate"] = client::window.getRefreshRate();
|
||||
// Miscellaneous
|
||||
@ -123,11 +161,46 @@ void client::initialize() {
|
||||
uf::renderer::states::resized = true;
|
||||
} );
|
||||
}
|
||||
#if !UF_ENV_DREAMCAST
|
||||
#if !UF_ENV_DREAMCAST && !UF_ENV_HEADLESS
|
||||
if ( client::config["window"]["mode"].as<std::string>() == "fullscreen" ) client::window.toggleFullscreen();
|
||||
else if ( client::config["window"]["mode"].as<std::string>() == "borderless" ) client::window.toggleFullscreen( true );
|
||||
#endif
|
||||
|
||||
#if UF_ENV_HEADLESS
|
||||
/* Headless command channel */ {
|
||||
// extra command so an agent can poke the engine from outside
|
||||
uf::console::registerCommand( "lua", "Executes a Lua snippet in the engine state", [&]( const uf::stl::string& code ) -> uf::stl::string {
|
||||
if ( code.empty() ) return "invalid invocation: lua '<code>'";
|
||||
#if UF_USE_LUA
|
||||
auto result = ext::lua::state.safe_script( code, sol::script_pass_on_error );
|
||||
if ( !result.valid() ) {
|
||||
sol::error err = result;
|
||||
return "Lua error: " + uf::stl::string( err.what() );
|
||||
}
|
||||
return "Lua executed";
|
||||
#else
|
||||
return "lua is not available in this build";
|
||||
#endif
|
||||
});
|
||||
|
||||
headlessIO = new HeadlessIO();
|
||||
headlessIO->running = true;
|
||||
std::thread( [](){
|
||||
uf::stl::string line;
|
||||
while ( headlessIO->running.load() && std::getline( std::cin, line ) ) {
|
||||
while ( !line.empty() && (line.back() == '\r' || line.back() == '\n') ) line.pop_back();
|
||||
if ( line.empty() ) continue;
|
||||
std::lock_guard<std::mutex> lock( headlessIO->mutex );
|
||||
headlessIO->queue.emplace_back( line );
|
||||
}
|
||||
std::lock_guard<std::mutex> lock( headlessIO->mutex );
|
||||
headlessIO->eof = true;
|
||||
}).detach();
|
||||
|
||||
UF_MSG_INFO("Headless mode: command channel ready (send command lines on stdin, 'help' for a list)");
|
||||
}
|
||||
#endif
|
||||
|
||||
client::ready = true;
|
||||
#if UF_ENV_DREAMCAST
|
||||
client::window.pollEvents();
|
||||
@ -135,6 +208,30 @@ void client::initialize() {
|
||||
}
|
||||
|
||||
void client::tick() {
|
||||
#if UF_ENV_HEADLESS
|
||||
// no window input; drain the stdin command channel on the main thread
|
||||
if ( headlessIO ) {
|
||||
uf::stl::vector<uf::stl::string> lines;
|
||||
bool announceEOF = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( headlessIO->mutex );
|
||||
lines.swap( headlessIO->queue );
|
||||
if ( headlessIO->eof && !headlessIO->eofAnnounced ) {
|
||||
headlessIO->eofAnnounced = true;
|
||||
announceEOF = true;
|
||||
}
|
||||
}
|
||||
for ( auto& line : lines ) {
|
||||
auto output = uf::console::execute( line );
|
||||
if ( !output.empty() ) UF_MSG_INFO("Console: {}", output);
|
||||
}
|
||||
if ( announceEOF ) {
|
||||
headlessIO->running = false;
|
||||
UF_MSG_INFO("Headless: stdin closed; command channel disabled ('quit' or SIGTERM stops the engine)");
|
||||
}
|
||||
}
|
||||
return;
|
||||
#else
|
||||
client::window.bufferInputs();
|
||||
client::window.pollEvents();
|
||||
|
||||
@ -177,6 +274,7 @@ void client::tick() {
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void client::render() {
|
||||
@ -184,5 +282,8 @@ void client::render() {
|
||||
}
|
||||
|
||||
void client::terminate() {
|
||||
#if UF_ENV_HEADLESS
|
||||
if ( headlessIO ) headlessIO->running = false;
|
||||
#endif
|
||||
client::window.terminate();
|
||||
}
|
||||
@ -6,6 +6,7 @@
|
||||
#include <uf/utils/window/payloads.h>
|
||||
|
||||
#include <uf/utils/memory/pool.h>
|
||||
#include <uf/utils/singletons/pre_main.h>
|
||||
#include <uf/spec/renderer/universal.h>
|
||||
|
||||
|
||||
@ -15,8 +16,13 @@
|
||||
|
||||
namespace {
|
||||
bool killing = true;
|
||||
volatile sig_atomic_t signalExit = 0;
|
||||
namespace handlers {
|
||||
|
||||
void term( int sig ) {
|
||||
signalExit = 1;
|
||||
}
|
||||
|
||||
void exit() {
|
||||
#if UF_ENV_DREAMCAST
|
||||
arch_stk_trace(1);
|
||||
@ -74,6 +80,7 @@ namespace {
|
||||
}
|
||||
|
||||
int main(int argc, char** argv){
|
||||
uf::StaticInitialization::runAll();
|
||||
for ( size_t i = 0; i < argc; ++i ) {
|
||||
char* c_str = argv[i];
|
||||
std::string string(argv[i]);
|
||||
@ -82,6 +89,8 @@ int main(int argc, char** argv){
|
||||
std::atexit(::handlers::exit);
|
||||
signal(SIGABRT, ::handlers::abrt);
|
||||
signal(SIGSEGV, ::handlers::segv);
|
||||
signal(SIGINT, ::handlers::term);
|
||||
signal(SIGTERM, ::handlers::term);
|
||||
|
||||
client::initialize();
|
||||
uf::initialize();
|
||||
@ -100,7 +109,7 @@ int main(int argc, char** argv){
|
||||
}
|
||||
}
|
||||
|
||||
while ( client::ready && uf::ready ) {
|
||||
while ( client::ready && uf::ready && !signalExit ) {
|
||||
++uf::time::frame;
|
||||
|
||||
#if UF_EXCEPTIONS
|
||||
|
||||
@ -12,7 +12,14 @@
|
||||
#undef UF_ENV_UNKNOWN
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32) || defined(__WIN32__) || defined(__CYGWIN__)
|
||||
#if defined(UF_HEADLESS)
|
||||
// Headless (null windowing + VK_EXT_headless_surface); targets Linux servers, so the Linux spec implementations stay active
|
||||
#define UF_ENV "Headless"
|
||||
#define UF_ENV_HEADLESS 1
|
||||
#define UF_ENV_LINUX 1
|
||||
#define UF_ENV_HEADER "null.h"
|
||||
|
||||
#elif defined(_WIN32) || defined(__WIN32__) || defined(__CYGWIN__)
|
||||
// Windows
|
||||
#define UF_ENV "Windows"
|
||||
#define UF_ENV_WINDOWS 1
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
|
||||
namespace uf {
|
||||
extern bool UF_API ready;
|
||||
extern bool UF_API headless;
|
||||
extern uf::stl::vector<uf::stl::string> UF_API arguments;
|
||||
|
||||
extern uf::Serializer UF_API config;
|
||||
|
||||
@ -59,6 +59,7 @@ namespace ext {
|
||||
VkInstance instance;
|
||||
VkDebugUtilsMessengerEXT debugMessenger;
|
||||
VkSurfaceKHR surface;
|
||||
bool surfaceless = false;
|
||||
VkPhysicalDevice physicalDevice;
|
||||
VkDevice logicalDevice;
|
||||
struct {
|
||||
|
||||
@ -15,6 +15,8 @@ namespace ext {
|
||||
|
||||
uf::stl::vector<VkSemaphore> presentCompleteSemaphores;
|
||||
uf::stl::vector<VkImage> images;
|
||||
// surfaceless (no VkSurfaceKHR): offscreen images stand in for swapchain images
|
||||
uf::stl::vector<VmaAllocation> allocations;
|
||||
|
||||
// helpers
|
||||
VkResult acquireNextImage( uint32_t* imageIndex, VkSemaphore, VkFence = VK_NULL_HANDLE );
|
||||
|
||||
6
engine/inc/uf/spec/context/null.h
Normal file
6
engine/inc/uf/spec/context/null.h
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <uf/config.h>
|
||||
|
||||
#include "linux.h"
|
||||
|
||||
5
engine/inc/uf/spec/controller/null.h
Normal file
5
engine/inc/uf/spec/controller/null.h
Normal file
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <uf/config.h>
|
||||
|
||||
#include "linux.h"
|
||||
6
engine/inc/uf/spec/null.h
Normal file
6
engine/inc/uf/spec/null.h
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <uf/config.h>
|
||||
|
||||
// reuse linux
|
||||
#include "linux.h"
|
||||
86
engine/inc/uf/spec/window/null.h
Normal file
86
engine/inc/uf/spec/window/null.h
Normal file
@ -0,0 +1,86 @@
|
||||
#pragma once
|
||||
|
||||
#include <uf/config.h>
|
||||
#include "universal.h"
|
||||
|
||||
#if UF_ENV_HEADLESS
|
||||
#if UF_USE_VULKAN
|
||||
#include <vulkan/vulkan.h>
|
||||
#endif
|
||||
|
||||
// these macros (from X11, pulled in by vulkan.h) interfere with other things
|
||||
#ifdef Success
|
||||
#undef Success
|
||||
#endif
|
||||
#ifdef None
|
||||
#undef None
|
||||
#endif
|
||||
|
||||
namespace spec {
|
||||
namespace null {
|
||||
// No windowing system; gives the engine a size to render at and
|
||||
// (when the driver supports it) a headless surface to render into
|
||||
class UF_API Window : public spec::uni::Window {
|
||||
public:
|
||||
typedef void* handle_t;
|
||||
typedef void* context_t;
|
||||
|
||||
protected:
|
||||
vector_t m_size;
|
||||
title_t m_title;
|
||||
|
||||
public:
|
||||
UF_API_CALL Window();
|
||||
UF_API_CALL Window( const vector_t& size, const title_t& title = "Window" );
|
||||
~Window();
|
||||
|
||||
void UF_API_CALL create( const vector_t& size, const title_t& title = "Window" );
|
||||
void UF_API_CALL terminate();
|
||||
|
||||
handle_t UF_API_CALL getDisplay() const;
|
||||
handle_t UF_API_CALL getHandle() const;
|
||||
vector_t UF_API_CALL getPosition() const;
|
||||
vector_t UF_API_CALL getSize() const;
|
||||
size_t UF_API_CALL getRefreshRate() const;
|
||||
|
||||
void UF_API_CALL setPosition( const vector_t& position );
|
||||
void UF_API_CALL centerWindow();
|
||||
void UF_API_CALL setMousePosition( const vector_t& position );
|
||||
vector_t UF_API_CALL getMousePosition();
|
||||
void UF_API_CALL setSize( const vector_t& size );
|
||||
void UF_API_CALL setTitle( const title_t& title );
|
||||
void UF_API_CALL setIcon( const vector_t& size, uint8_t* pixels );
|
||||
void UF_API_CALL setVisible( bool visibility );
|
||||
void UF_API_CALL setCursorVisible( bool visibility );
|
||||
void UF_API_CALL setKeyRepeatEnabled( bool state );
|
||||
void UF_API_CALL setMouseGrabbed( bool state );
|
||||
|
||||
static bool UF_API_CALL isKeyPressed( const uf::stl::string& key );
|
||||
|
||||
void UF_API_CALL requestFocus();
|
||||
bool UF_API_CALL hasFocus() const;
|
||||
|
||||
void UF_API_CALL bufferInputs();
|
||||
void UF_API_CALL processEvents();
|
||||
bool UF_API_CALL pollEvents( bool block = false );
|
||||
void UF_API_CALL grabMouse( bool state );
|
||||
|
||||
static pod::Vector2ui UF_API_CALL getResolution();
|
||||
void UF_API_CALL toggleFullscreen( bool borderless = false );
|
||||
|
||||
#if UF_USE_VULKAN
|
||||
uf::stl::vector<uf::stl::string> UF_API_CALL getExtensions( bool validationEnabled );
|
||||
void UF_API_CALL createSurface( VkInstance instance, VkSurfaceKHR& surface );
|
||||
#endif
|
||||
|
||||
void display();
|
||||
};
|
||||
}
|
||||
typedef spec::null::Window Window;
|
||||
}
|
||||
|
||||
namespace uf {
|
||||
using Window = spec::null::Window;
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -7,5 +7,6 @@ namespace uf {
|
||||
struct UF_API StaticInitialization {
|
||||
public:
|
||||
StaticInitialization(std::function<void()>);
|
||||
static void UF_API runAll();
|
||||
};
|
||||
}
|
||||
@ -45,9 +45,9 @@
|
||||
|
||||
namespace uf {
|
||||
namespace thread {
|
||||
extern UF_API uf::stl::string mainThreadName;
|
||||
extern UF_API uf::stl::string workerThreadName;
|
||||
extern UF_API uf::stl::string asyncThreadName;
|
||||
inline constexpr const char* mainThreadName = "Main";
|
||||
inline constexpr const char* workerThreadName = "Worker";
|
||||
inline constexpr const char* asyncThreadName = "Async";
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,6 +155,7 @@ namespace uf {
|
||||
|
||||
/* Easy to use async helper functions */
|
||||
pod::Thread& UF_API fetchWorker( const uf::stl::string& name = uf::thread::workerThreadName );
|
||||
inline pod::Thread& UF_API fetchWorker( const char* s ) { return fetchWorker( uf::stl::string( s ) ); } // ugh
|
||||
pod::Thread::Tasks UF_API schedule( bool multithread, bool waits = true );
|
||||
pod::Thread::Tasks UF_API schedule( const uf::stl::string& name = uf::thread::workerThreadName, bool waits = true );
|
||||
std::shared_ptr<pod::Thread::Tasks::Tracker> UF_API execute( pod::Thread::Tasks& tasks );
|
||||
|
||||
@ -75,7 +75,8 @@ void uf::asset::processQueue() {
|
||||
|
||||
if ( jobs.empty() && finishedJobs.empty() ) return;
|
||||
|
||||
auto tasks = uf::thread::schedule(uf::asset::asyncQueue ? uf::thread::asyncThreadName : uf::thread::mainThreadName, false);
|
||||
// to-do: check if const char* overload works again
|
||||
auto tasks = uf::thread::schedule( uf::stl::string( uf::asset::asyncQueue ? uf::thread::asyncThreadName : uf::thread::mainThreadName ), false );
|
||||
|
||||
if ( !finishedJobs.empty() ) {
|
||||
tasks.queue([jobs = std::move(finishedJobs)]() {
|
||||
@ -125,7 +126,8 @@ void uf::asset::processIO( const uf::asset::Stream::container_t& pendingStreams,
|
||||
return uf::asset::processIO( {}, pendingStreams, async, wait );
|
||||
}
|
||||
void uf::asset::processIO( const uf::asset::Read::container_t& pendingReads, const uf::asset::Stream::container_t& pendingStreams, bool async, bool wait ) {
|
||||
auto tasks = uf::thread::schedule(async ? uf::thread::asyncThreadName : uf::thread::mainThreadName, wait);
|
||||
// to-do: check if const char* overload works again
|
||||
auto tasks = uf::thread::schedule( uf::stl::string( async ? uf::thread::asyncThreadName : uf::thread::mainThreadName ), wait );
|
||||
if ( pendingReads.empty() && pendingStreams.empty() ) return;
|
||||
|
||||
for ( auto& [filename, requests] : pendingReads ) {
|
||||
|
||||
@ -52,6 +52,11 @@
|
||||
#endif
|
||||
|
||||
bool uf::ready = false;
|
||||
#if UF_ENV_HEADLESS
|
||||
bool uf::headless = true;
|
||||
#else
|
||||
bool uf::headless = false;
|
||||
#endif
|
||||
uf::stl::vector<uf::stl::string> uf::arguments;
|
||||
uf::Serializer uf::config;
|
||||
|
||||
|
||||
@ -160,6 +160,8 @@ namespace {
|
||||
}
|
||||
//
|
||||
{
|
||||
if ( device.surfaceless ) return deviceInfo;
|
||||
|
||||
VkSurfaceCapabilitiesKHR capabilities;
|
||||
uf::stl::vector<VkSurfaceFormatKHR> formats;
|
||||
uf::stl::vector<VkPresentModeKHR> presentModes;
|
||||
@ -970,6 +972,19 @@ void ext::vulkan::Device::initialize() {
|
||||
|
||||
validateRequestedExtensions( extensions.properties.instance, requestedExtensions, extensions.supported.instance );
|
||||
}
|
||||
//
|
||||
#if UF_ENV_HEADLESS
|
||||
// can't create a headless surface; render offscreen with no VkSurfaceKHR and no swapchain
|
||||
if ( std::find( extensions.supported.instance.begin(), extensions.supported.instance.end(), uf::stl::string( VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME ) ) == extensions.supported.instance.end() ) {
|
||||
this->surfaceless = true;
|
||||
UF_MSG_ERROR("Driver does not expose VK_EXT_headless_surface; continuing surfaceless -- offscreen rendering, no presentation (no VkSurfaceKHR, no swapchain)");
|
||||
// un-request the surface extensions so the instance can be created without them
|
||||
requestedExtensions.erase( std::remove( requestedExtensions.begin(), requestedExtensions.end(), uf::stl::string( VK_KHR_SURFACE_EXTENSION_NAME ) ), requestedExtensions.end() );
|
||||
requestedExtensions.erase( std::remove( requestedExtensions.begin(), requestedExtensions.end(), uf::stl::string( VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME ) ), requestedExtensions.end() );
|
||||
extensions.supported.instance.erase( std::remove( extensions.supported.instance.begin(), extensions.supported.instance.end(), uf::stl::string( VK_KHR_SURFACE_EXTENSION_NAME ) ), extensions.supported.instance.end() );
|
||||
extensions.supported.instance.erase( std::remove( extensions.supported.instance.begin(), extensions.supported.instance.end(), uf::stl::string( VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME ) ), extensions.supported.instance.end() );
|
||||
}
|
||||
#endif
|
||||
// Create instance
|
||||
{
|
||||
uf::stl::vector<uf::stl::string> instanceExtensions;
|
||||
@ -1018,7 +1033,13 @@ void ext::vulkan::Device::initialize() {
|
||||
createInfo.enabledLayerCount = static_cast<uint32_t>(cInstanceLayers.size());
|
||||
createInfo.ppEnabledLayerNames = cInstanceLayers.data();
|
||||
|
||||
#if UF_ENV_HEADLESS
|
||||
// lacks VK_EXT_headless_surface, so a failure here is generic and unexpected
|
||||
VkResult instanceResult = vkCreateInstance( &createInfo, nullptr, &this->instance );
|
||||
if ( instanceResult != VK_SUCCESS ) UF_EXCEPTION("{}", ext::vulkan::errorString( instanceResult ));
|
||||
#else
|
||||
VK_CHECK_RESULT( vkCreateInstance( &createInfo, nullptr, &this->instance ));
|
||||
#endif
|
||||
VK_REGISTER_HANDLE( this->instance );
|
||||
|
||||
{
|
||||
@ -1043,7 +1064,7 @@ void ext::vulkan::Device::initialize() {
|
||||
}
|
||||
// Create surface
|
||||
{
|
||||
window->createSurface( instance, surface );
|
||||
if ( !surfaceless ) window->createSurface( instance, surface );
|
||||
}
|
||||
// Create physical device
|
||||
|
||||
@ -1162,9 +1183,8 @@ void ext::vulkan::Device::initialize() {
|
||||
|
||||
validateRequestedExtensions( extensions.properties.device, requestedExtensions, extensions.supported.device );
|
||||
}
|
||||
uf::stl::vector<uf::stl::string> deviceExtensions = {
|
||||
VK_KHR_SWAPCHAIN_EXTENSION_NAME
|
||||
};
|
||||
uf::stl::vector<uf::stl::string> deviceExtensions;
|
||||
if ( !surfaceless ) deviceExtensions.emplace_back( VK_KHR_SWAPCHAIN_EXTENSION_NAME );
|
||||
for ( auto& s : extensions.supported.device ) {
|
||||
VK_VALIDATION_MESSAGE("Enabled device extension: {}", s);
|
||||
deviceExtensions.emplace_back( s );
|
||||
@ -1222,7 +1242,7 @@ void ext::vulkan::Device::initialize() {
|
||||
}
|
||||
|
||||
VkBool32 presentSupport = false;
|
||||
vkGetPhysicalDeviceSurfaceSupportKHR( this->physicalDevice, i, surface, &presentSupport );
|
||||
if ( !surfaceless ) vkGetPhysicalDeviceSurfaceSupportKHR( this->physicalDevice, i, surface, &presentSupport );
|
||||
if ( queueFamily.queueCount > 0 && presentSupport ) {
|
||||
presentQueueNodeIndex = i;
|
||||
}
|
||||
@ -1282,7 +1302,7 @@ void ext::vulkan::Device::initialize() {
|
||||
}
|
||||
|
||||
// Create the logical device representation
|
||||
if ( useSwapChain ) {
|
||||
if ( useSwapChain && !surfaceless ) {
|
||||
// If the device will be used for presenting to a display via a swapchain we need to request the swapchain extension
|
||||
deviceExtensions.emplace_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
|
||||
}
|
||||
@ -1490,50 +1510,63 @@ void ext::vulkan::Device::initialize() {
|
||||
}
|
||||
// Set formats
|
||||
{
|
||||
uf::stl::vector<VkSurfaceFormatKHR> formats;
|
||||
uint32_t formatCount; vkGetPhysicalDeviceSurfaceFormatsKHR( this->physicalDevice, device.surface, &formatCount, nullptr);
|
||||
formats.resize( formatCount );
|
||||
vkGetPhysicalDeviceSurfaceFormatsKHR( this->physicalDevice, device.surface, &formatCount, formats.data() );
|
||||
|
||||
bool SRGB = true;
|
||||
auto TARGET_FORMAT = SRGB ? VK_FORMAT_B8G8R8A8_SRGB : VK_FORMAT_B8G8R8A8_UNORM;
|
||||
auto TARGET_COLORSPACE = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
|
||||
if ( ext::vulkan::settings::pipelines::hdr ) {
|
||||
TARGET_FORMAT = VK_FORMAT_R32G32B32A32_SFLOAT;
|
||||
TARGET_COLORSPACE = VK_COLOR_SPACE_HDR10_ST2084_EXT;
|
||||
}
|
||||
|
||||
// If the surface format list only includes one entry with VK_FORMAT_UNDEFINED,
|
||||
// there is no preferered format, so we assume VK_FORMAT_B8G8R8A8_SRGB
|
||||
if ( formatCount == 1 && formats[0].format == VK_FORMAT_UNDEFINED ) {
|
||||
ext::vulkan::settings::formats::color = TARGET_FORMAT;
|
||||
ext::vulkan::settings::formats::colorSpace = formats[0].colorSpace;
|
||||
} else {
|
||||
// iterate over the list of available surface format and
|
||||
// check for the presence of VK_FORMAT_B8G8R8A8_SRGB
|
||||
bool found = false;
|
||||
for ( auto&& surfaceFormat : formats ) {
|
||||
if ( surfaceFormat.format == ext::vulkan::settings::formats::color ) {
|
||||
ext::vulkan::settings::formats::color = surfaceFormat.format;
|
||||
ext::vulkan::settings::formats::colorSpace = surfaceFormat.colorSpace;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if ( surfaceless ) {
|
||||
// no surface to query formats from; assume the B8G8R8A8_SRGB (or HDR) default
|
||||
bool SRGB = true;
|
||||
auto TARGET_FORMAT = SRGB ? VK_FORMAT_B8G8R8A8_SRGB : VK_FORMAT_B8G8R8A8_UNORM;
|
||||
auto TARGET_COLORSPACE = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
|
||||
if ( ext::vulkan::settings::pipelines::hdr ) {
|
||||
TARGET_FORMAT = VK_FORMAT_R32G32B32A32_SFLOAT;
|
||||
TARGET_COLORSPACE = VK_COLOR_SPACE_HDR10_ST2084_EXT;
|
||||
}
|
||||
if ( !found ) {
|
||||
ext::vulkan::settings::formats::color = TARGET_FORMAT;
|
||||
ext::vulkan::settings::formats::colorSpace = TARGET_COLORSPACE;
|
||||
} else {
|
||||
uf::stl::vector<VkSurfaceFormatKHR> formats;
|
||||
uint32_t formatCount; vkGetPhysicalDeviceSurfaceFormatsKHR( this->physicalDevice, device.surface, &formatCount, nullptr);
|
||||
formats.resize( formatCount );
|
||||
vkGetPhysicalDeviceSurfaceFormatsKHR( this->physicalDevice, device.surface, &formatCount, formats.data() );
|
||||
|
||||
bool SRGB = true;
|
||||
auto TARGET_FORMAT = SRGB ? VK_FORMAT_B8G8R8A8_SRGB : VK_FORMAT_B8G8R8A8_UNORM;
|
||||
auto TARGET_COLORSPACE = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
|
||||
if ( ext::vulkan::settings::pipelines::hdr ) {
|
||||
TARGET_FORMAT = VK_FORMAT_R32G32B32A32_SFLOAT;
|
||||
TARGET_COLORSPACE = VK_COLOR_SPACE_HDR10_ST2084_EXT;
|
||||
}
|
||||
|
||||
// If the surface format list only includes one entry with VK_FORMAT_UNDEFINED,
|
||||
// there is no preferered format, so we assume VK_FORMAT_B8G8R8A8_SRGB
|
||||
if ( formatCount == 1 && formats[0].format == VK_FORMAT_UNDEFINED ) {
|
||||
ext::vulkan::settings::formats::color = TARGET_FORMAT;
|
||||
ext::vulkan::settings::formats::colorSpace = formats[0].colorSpace;
|
||||
} else {
|
||||
// iterate over the list of available surface format and
|
||||
// check for the presence of VK_FORMAT_B8G8R8A8_SRGB
|
||||
bool found = false;
|
||||
for ( auto&& surfaceFormat : formats ) {
|
||||
if ( surfaceFormat.format == TARGET_FORMAT ) {
|
||||
if ( surfaceFormat.format == ext::vulkan::settings::formats::color ) {
|
||||
ext::vulkan::settings::formats::color = surfaceFormat.format;
|
||||
ext::vulkan::settings::formats::colorSpace = surfaceFormat.colorSpace;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// in case VK_FORMAT_B8G8R8A8_SRGB is not available
|
||||
// select the first available color format
|
||||
if ( !found ) {
|
||||
ext::vulkan::settings::formats::color = formats[0].format;
|
||||
ext::vulkan::settings::formats::colorSpace = formats[0].colorSpace;
|
||||
for ( auto&& surfaceFormat : formats ) {
|
||||
if ( surfaceFormat.format == TARGET_FORMAT ) {
|
||||
ext::vulkan::settings::formats::color = surfaceFormat.format;
|
||||
ext::vulkan::settings::formats::colorSpace = surfaceFormat.colorSpace;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// in case VK_FORMAT_B8G8R8A8_SRGB is not available
|
||||
// select the first available color format
|
||||
if ( !found ) {
|
||||
ext::vulkan::settings::formats::color = formats[0].format;
|
||||
ext::vulkan::settings::formats::colorSpace = formats[0].colorSpace;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,14 +67,16 @@ void ext::vulkan::BaseRenderMode::initialize( Device& device ) {
|
||||
renderTarget.initialize( device );
|
||||
|
||||
// set sync objects
|
||||
for ( auto i = 0; i < ext::vulkan::swapchain.buffers; ++i ) {
|
||||
auto& presentCompleteSemaphore = swapchain.presentCompleteSemaphores.emplace_back();
|
||||
VkSemaphoreCreateInfo semaphoreCreateInfo = {};
|
||||
semaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
|
||||
semaphoreCreateInfo.pNext = nullptr;
|
||||
if ( !device.surfaceless ) {
|
||||
for ( auto i = 0; i < ext::vulkan::swapchain.buffers; ++i ) {
|
||||
auto& presentCompleteSemaphore = swapchain.presentCompleteSemaphores.emplace_back();
|
||||
VkSemaphoreCreateInfo semaphoreCreateInfo = {};
|
||||
semaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
|
||||
semaphoreCreateInfo.pNext = nullptr;
|
||||
|
||||
VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &presentCompleteSemaphore));
|
||||
VK_REGISTER_HANDLE(presentCompleteSemaphore);
|
||||
VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &presentCompleteSemaphore));
|
||||
VK_REGISTER_HANDLE(presentCompleteSemaphore);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -109,8 +111,13 @@ VkSubmitInfo ext::vulkan::BaseRenderMode::queue() {
|
||||
VkSubmitInfo submitInfo = {};
|
||||
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||
submitInfo.pWaitDstStageMask = waitStageMask;
|
||||
submitInfo.pWaitSemaphores = &swapchain.presentCompleteSemaphores[states::currentBuffer];
|
||||
submitInfo.waitSemaphoreCount = 1;
|
||||
if ( device->surfaceless ) {
|
||||
submitInfo.pWaitSemaphores = nullptr;
|
||||
submitInfo.waitSemaphoreCount = 0;
|
||||
} else {
|
||||
submitInfo.pWaitSemaphores = &swapchain.presentCompleteSemaphores[states::currentBuffer];
|
||||
submitInfo.waitSemaphoreCount = 1;
|
||||
}
|
||||
// the present-wait semaphore is paired with the acquired image, so it is only reused once that image is re-acquired
|
||||
submitInfo.pSignalSemaphores = &renderCompleteSemaphores[states::imageIndex];
|
||||
submitInfo.signalSemaphoreCount = 1;
|
||||
@ -135,10 +142,12 @@ void ext::vulkan::BaseRenderMode::render() {
|
||||
VK_CHECK_QUEUE_CHECKPOINT( queue, res );
|
||||
}
|
||||
|
||||
{
|
||||
VkQueue queue = device->getQueue( QueueEnum::PRESENT );
|
||||
auto lock = device->lockQueue( queue );
|
||||
VK_CHECK_RESULT(swapchain.queuePresent( queue, states::imageIndex, renderCompleteSemaphores[states::imageIndex]));
|
||||
if ( !device->surfaceless ) {
|
||||
{
|
||||
VkQueue queue = device->getQueue( QueueEnum::PRESENT );
|
||||
auto lock = device->lockQueue( queue );
|
||||
VK_CHECK_RESULT(swapchain.queuePresent( queue, states::imageIndex, renderCompleteSemaphores[states::imageIndex]));
|
||||
}
|
||||
}
|
||||
|
||||
states::currentBuffer = (states::currentBuffer + 1) % ext::vulkan::swapchain.buffers;
|
||||
@ -170,7 +179,12 @@ void ext::vulkan::BaseRenderMode::createCommandBuffers( const uf::stl::vector<ex
|
||||
void ext::vulkan::BaseRenderMode::_acquire() {
|
||||
if ( ::acquired ) return;
|
||||
VK_CHECK_RESULT(vkWaitForFences(*device, 1, &fences[states::currentBuffer], VK_TRUE, VK_DEFAULT_FENCE_TIMEOUT));
|
||||
VK_CHECK_RESULT(swapchain.acquireNextImage(&states::imageIndex, swapchain.presentCompleteSemaphores[states::currentBuffer]));
|
||||
if ( device->surfaceless ) {
|
||||
// no swapchain to acquire; the offscreen pool is double-buffered by currentBuffer
|
||||
states::imageIndex = states::currentBuffer;
|
||||
} else {
|
||||
VK_CHECK_RESULT(swapchain.acquireNextImage(&states::imageIndex, swapchain.presentCompleteSemaphores[states::currentBuffer]));
|
||||
}
|
||||
VK_CHECK_RESULT(vkResetFences(*device, 1, &fences[states::currentBuffer]));
|
||||
::acquired = true;
|
||||
}
|
||||
|
||||
@ -7,6 +7,10 @@
|
||||
#include <uf/ext/ffx/fsr.h>
|
||||
|
||||
VkResult ext::vulkan::Swapchain::acquireNextImage( uint32_t* imageIndex, VkSemaphore presentCompleteSemaphore, VkFence acquireFence ) {
|
||||
if ( device && device->surfaceless ) {
|
||||
*imageIndex = ext::vulkan::states::currentBuffer;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
#if UF_USE_FFX_FSR || UF_USE_FFX_SDK
|
||||
if ( ext::fsr::frameInterpolation ) {
|
||||
return ext::fsr::acquireNextImage( imageIndex, presentCompleteSemaphore, acquireFence );
|
||||
@ -17,6 +21,7 @@ VkResult ext::vulkan::Swapchain::acquireNextImage( uint32_t* imageIndex, VkSemap
|
||||
}
|
||||
|
||||
VkResult ext::vulkan::Swapchain::queuePresent( VkQueue queue, uint32_t imageIndex, VkSemaphore waitSemaphore ) {
|
||||
if ( device && device->surfaceless ) return VK_SUCCESS;
|
||||
#if UF_USE_FFX_FSR || UF_USE_FFX_SDK
|
||||
if ( ext::fsr::frameInterpolation ) {
|
||||
return ext::fsr::queuePresent( queue, imageIndex, waitSemaphore );
|
||||
@ -43,6 +48,49 @@ void ext::vulkan::Swapchain::initialize( Device& device ) {
|
||||
// if ( width == 0 ) width = ext::vulkan::settings::width;
|
||||
// if ( height == 0 ) height = ext::vulkan::settings::height;
|
||||
}
|
||||
// a pool of offscreen images and presenting is a no-op
|
||||
if ( device.surfaceless ) {
|
||||
// a re-initialization (e.g. a resize) replaces the previous pool
|
||||
for ( size_t i = 0; i < images.size(); ++i ) {
|
||||
if ( images[i] != VK_NULL_HANDLE ) {
|
||||
vmaDestroyImage( ext::vulkan::allocator, images[i], allocations[i] );
|
||||
VK_UNREGISTER_HANDLE( images[i] );
|
||||
}
|
||||
}
|
||||
images.clear();
|
||||
allocations.clear();
|
||||
|
||||
buffers = 2;
|
||||
images.resize( buffers );
|
||||
allocations.resize( buffers );
|
||||
|
||||
auto size = device.window->getSize();
|
||||
for ( auto i = 0u; i < buffers; ++i ) {
|
||||
VkImageCreateInfo imageCreateInfo = {};
|
||||
imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
|
||||
imageCreateInfo.format = ext::vulkan::settings::formats::color;
|
||||
imageCreateInfo.extent = {
|
||||
static_cast<uint32_t>( size[0] ),
|
||||
static_cast<uint32_t>( size[1] ),
|
||||
1
|
||||
};
|
||||
imageCreateInfo.mipLevels = 1;
|
||||
imageCreateInfo.arrayLayers = 1;
|
||||
imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
imageCreateInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
|
||||
VmaAllocationCreateInfo allocationCreateInfo = {};
|
||||
allocationCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
|
||||
|
||||
VK_CHECK_RESULT(vmaCreateImage( ext::vulkan::allocator, &imageCreateInfo, &allocationCreateInfo, &images[i], &allocations[i], nullptr ));
|
||||
VK_REGISTER_HANDLE( images[i] );
|
||||
}
|
||||
UF_MSG_INFO("Surfaceless swapchain: {} offscreen image(s) at {}x{}", buffers, size[0], size[1] );
|
||||
return;
|
||||
}
|
||||
// Set present
|
||||
VkPresentModeKHR swapchainPresentMode = VK_PRESENT_MODE_FIFO_KHR;
|
||||
{
|
||||
@ -215,6 +263,21 @@ void ext::vulkan::Swapchain::initialize( Device& device ) {
|
||||
void ext::vulkan::Swapchain::destroy() {
|
||||
if ( !device ) return;
|
||||
|
||||
if ( device->surfaceless ) {
|
||||
// offscreen images are regular VMA allocations; image and memory go together
|
||||
for ( size_t i = 0; i < swapchain.images.size(); ++i ) {
|
||||
if ( swapchain.images[i] != VK_NULL_HANDLE ) {
|
||||
vmaDestroyImage( ext::vulkan::allocator, swapchain.images[i], swapchain.allocations[i] );
|
||||
VK_UNREGISTER_HANDLE( swapchain.images[i] );
|
||||
swapchain.images[i] = VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
swapchain.images.clear();
|
||||
swapchain.allocations.clear();
|
||||
device = VK_NULL_HANDLE;
|
||||
return;
|
||||
}
|
||||
|
||||
for ( auto& image : swapchain.images ) {
|
||||
// vkDestroyImage( *device, image, nullptr ); // destroyed via vkDestroySwapchainKHR
|
||||
//VK_UNREGISTER_HANDLE( image );
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
#include <uf/spec/window/window.h>
|
||||
|
||||
#if UF_ENV_LINUX
|
||||
#if UF_ENV_LINUX && !UF_ENV_HEADLESS
|
||||
#include <uf/utils/io/inputs.h>
|
||||
#include <uf/utils/io/iostream.h>
|
||||
#include <uf/utils/serialize/serializer.h>
|
||||
|
||||
123
engine/src/spec/window/null.cpp
Normal file
123
engine/src/spec/window/null.cpp
Normal file
@ -0,0 +1,123 @@
|
||||
#include <uf/spec/window/window.h>
|
||||
|
||||
#if UF_ENV_HEADLESS
|
||||
#include <uf/utils/io/iostream.h>
|
||||
|
||||
#if UF_USE_VULKAN
|
||||
#include <uf/ext/vulkan/vulkan.h>
|
||||
#endif
|
||||
|
||||
namespace spec {
|
||||
namespace null {
|
||||
Window::Window() : Window( { 1280, 720 } ) {
|
||||
}
|
||||
Window::Window( const vector_t& size, const title_t& title ) {
|
||||
create( size, title );
|
||||
}
|
||||
Window::~Window() {
|
||||
}
|
||||
|
||||
void Window::create( const vector_t& size, const title_t& title ) {
|
||||
// there is nothing to create; just remember what the client wants
|
||||
m_size = size;
|
||||
m_title = title;
|
||||
}
|
||||
void Window::terminate() {
|
||||
}
|
||||
|
||||
Window::handle_t Window::getDisplay() const {
|
||||
return nullptr;
|
||||
}
|
||||
Window::handle_t Window::getHandle() const {
|
||||
return nullptr;
|
||||
}
|
||||
spec::null::Window::vector_t Window::getPosition() const {
|
||||
return { 0, 0 };
|
||||
}
|
||||
spec::null::Window::vector_t Window::getSize() const {
|
||||
// drives the swapchain extent fallback for unbounded surfaces
|
||||
return m_size;
|
||||
}
|
||||
size_t Window::getRefreshRate() const {
|
||||
// no display to query; pretend 60Hz
|
||||
return 60;
|
||||
}
|
||||
|
||||
void Window::setPosition( const vector_t& position ) {
|
||||
}
|
||||
void Window::centerWindow() {
|
||||
}
|
||||
void Window::setMousePosition( const vector_t& position ) {
|
||||
}
|
||||
spec::null::Window::vector_t Window::getMousePosition() {
|
||||
return { 0, 0 };
|
||||
}
|
||||
void Window::setSize( const vector_t& size ) {
|
||||
m_size = size;
|
||||
}
|
||||
void Window::setTitle( const title_t& title ) {
|
||||
m_title = title;
|
||||
}
|
||||
void Window::setIcon( const vector_t& size, uint8_t* pixels ) {
|
||||
}
|
||||
void Window::setVisible( bool visibility ) {
|
||||
}
|
||||
void Window::setCursorVisible( bool visibility ) {
|
||||
}
|
||||
void Window::setKeyRepeatEnabled( bool state ) {
|
||||
}
|
||||
void Window::setMouseGrabbed( bool state ) {
|
||||
}
|
||||
|
||||
bool Window::isKeyPressed( const uf::stl::string& key ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void Window::requestFocus() {
|
||||
}
|
||||
bool Window::hasFocus() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void Window::bufferInputs() {
|
||||
}
|
||||
void Window::processEvents() {
|
||||
}
|
||||
bool Window::pollEvents( bool block ) {
|
||||
return false;
|
||||
}
|
||||
void Window::grabMouse( bool state ) {
|
||||
}
|
||||
|
||||
pod::Vector2ui Window::getResolution() {
|
||||
return { 1280, 720 };
|
||||
}
|
||||
void Window::toggleFullscreen( bool borderless ) {
|
||||
}
|
||||
|
||||
#if UF_USE_VULKAN
|
||||
uf::stl::vector<uf::stl::string> Window::getExtensions( bool validationEnabled ) {
|
||||
uf::stl::vector<uf::stl::string> exts = { VK_KHR_SURFACE_EXTENSION_NAME, VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME };
|
||||
if ( validationEnabled ) exts.push_back( VK_EXT_DEBUG_UTILS_EXTENSION_NAME );
|
||||
return exts;
|
||||
}
|
||||
void Window::createSurface( VkInstance instance, VkSurfaceKHR& surface ) {
|
||||
// the extent is unbounded by spec; the swapchain falls back to getSize()
|
||||
VkHeadlessSurfaceCreateInfoEXT info = {};
|
||||
info.sType = VK_STRUCTURE_TYPE_HEADLESS_SURFACE_CREATE_INFO_EXT;
|
||||
|
||||
{
|
||||
VkResult result = vkCreateHeadlessSurfaceEXT( instance, &info, nullptr, &surface );
|
||||
if ( result == VK_ERROR_EXTENSION_NOT_PRESENT ) {
|
||||
UF_MSG_ERROR("Driver does not expose VK_EXT_headless_surface; the headless build requires a driver that does (Mesa/AMD/Intel, not NVIDIA proprietary)");
|
||||
}
|
||||
VK_CHECK_RESULT(result);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void Window::display() {
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -1,5 +1,20 @@
|
||||
#include <uf/utils/singletons/pre_main.h>
|
||||
|
||||
#include <uf/utils/memory/vector.h>
|
||||
|
||||
namespace {
|
||||
// queue of deferred initializers; the pointer is constant-initialized to NULL, so it is safe to touch during static initialization
|
||||
uf::stl::vector<std::function<void()>>* queuedInitializations = NULL;
|
||||
}
|
||||
|
||||
uf::StaticInitialization::StaticInitialization( std::function<void()> fun ) {
|
||||
if ( fun ) fun();
|
||||
if ( queuedInitializations == NULL ) queuedInitializations = new uf::stl::vector<std::function<void()>>;
|
||||
queuedInitializations->emplace_back( std::move(fun) );
|
||||
}
|
||||
|
||||
void uf::StaticInitialization::runAll() {
|
||||
if ( queuedInitializations == NULL ) return;
|
||||
auto& queue = *queuedInitializations;
|
||||
for ( auto& initializer : queue ) if ( initializer ) initializer();
|
||||
queue.clear();
|
||||
}
|
||||
@ -14,9 +14,6 @@ float uf::thread::limiter = 1.0f / 120.0f;
|
||||
uint32_t uf::thread::workers = 1;
|
||||
uf::thread::id_t uf::thread::mainThreadId = nullptr;
|
||||
bool uf::thread::async = false;
|
||||
uf::stl::string uf::thread::mainThreadName = "Main";
|
||||
uf::stl::string uf::thread::workerThreadName = "Worker";
|
||||
uf::stl::string uf::thread::asyncThreadName = "Async";
|
||||
|
||||
namespace {
|
||||
mutex_t global_mutex = MUTEX_INITIALIZER;
|
||||
@ -77,7 +74,8 @@ pod::Thread& uf::thread::fetchWorker( const uf::stl::string& name ) {
|
||||
}
|
||||
|
||||
pod::Thread::Tasks uf::thread::schedule( bool async, bool wait ) {
|
||||
return schedule( async ? uf::thread::asyncThreadName : uf::thread::mainThreadName, wait );
|
||||
// to-do: check if const char* overload works again
|
||||
return schedule( uf::stl::string( async ? uf::thread::asyncThreadName : uf::thread::mainThreadName ), wait );
|
||||
}
|
||||
|
||||
pod::Thread::Tasks uf::thread::schedule( const uf::stl::string& name, bool wait ) {
|
||||
|
||||
@ -8,9 +8,6 @@ float uf::thread::limiter = 1.0f / 120.0f;
|
||||
uint32_t uf::thread::workers = 1;
|
||||
uf::thread::id_t uf::thread::mainThreadId = std::this_thread::get_id();
|
||||
bool uf::thread::async = false;
|
||||
uf::stl::string uf::thread::mainThreadName = "Main";
|
||||
uf::stl::string uf::thread::workerThreadName = "Worker";
|
||||
uf::stl::string uf::thread::asyncThreadName = "Async";
|
||||
|
||||
namespace {
|
||||
std::mutex mutex;
|
||||
@ -56,7 +53,8 @@ pod::Thread& uf::thread::fetchWorker( const uf::stl::string& name ) {
|
||||
UF_EXCEPTION("cannot find free worker");
|
||||
}
|
||||
pod::Thread::Tasks uf::thread::schedule( bool async, bool wait ) {
|
||||
return schedule( async ? uf::thread::workerThreadName : uf::thread::mainThreadName, wait );
|
||||
// to-do: check if const char* overload works again
|
||||
return schedule( uf::stl::string( async ? uf::thread::workerThreadName : uf::thread::mainThreadName ), wait );
|
||||
}
|
||||
pod::Thread::Tasks uf::thread::schedule( const uf::stl::string& name, bool wait ) {
|
||||
pod::Thread::Tasks tasks = {
|
||||
|
||||
@ -9,6 +9,9 @@ export PATH="$(pwd)/exe/lib/${ARCH}/:$(pwd)/exe/lib/${ARCH}/${CC}/:$(pwd)/exe/li
|
||||
export LD_LIBRARY_PATH="$(pwd)/exe/lib/${ARCH}/:$(pwd)/exe/lib/${ARCH}/${CC}/:$(pwd)/exe/lib/${ARCH}/${CC}/${RENDERER}/:${LD_LIBRARY_PATH}"
|
||||
|
||||
echo PATH: ${PATH}
|
||||
echo Executing ./exe/program.${ARCH}.${CC}.${RENDERER}.exe $@
|
||||
./exe/program.${ARCH}.${CC}.${RENDERER}.exe $@
|
||||
NAME=./exe/program.${ARCH}.${CC}.${RENDERER}
|
||||
# Windows builds carry a .exe suffix; platform builds (e.g. Linux) do not
|
||||
[ -f "${NAME}.exe" ] && NAME="${NAME}.exe"
|
||||
echo Executing ${NAME} $@
|
||||
${NAME} $@
|
||||
tskill program
|
||||
|
||||
Loading…
Reference in New Issue
Block a user