This commit is contained in:
2026-06-14 19:09:18 +01:00
parent 14bd1a9271
commit 13fa90a0e9
3958 changed files with 999286 additions and 4 deletions
+270
View File
@@ -0,0 +1,270 @@
# Copyright 2017-2020 The Khronos Group Inc.
# SPDX-License-Identifier: Apache-2.0
#if(WIN32)
# cmake_print_variables(
# CMAKE_SYSTEM_VERSION
# CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION
# )
#endif()
# N.B. Do not set CMAKE_XCODE_GENERATE_SCHEME. If set, CMake will
# destroy the scheme, losing any user-made settings, when generating
# the project after the cache has been deleted. Cache deletion is
# needed whenever Xcode is updated with new SDK versions due to CMake
# hard-wiring the version in many of its variables.
#
# XCODE_SCHEME settings can be found at
# https://cmake.org/cmake/help/latest/prop_tgt/XCODE_GENERATE_SCHEME.html
# These settings are recommended by MoltenVK Runtime User Guide.
set( CMAKE_XCODE_SCHEME_ENABLE_GPU_API_VALIDATION FALSE )
set( CMAKE_XCODE_SCHEME_ENABLE_GPU_FRAME_CAPTURE_MODE DISABLED )
#set(CMAKE_FIND_DEBUG_MODE TRUE)
find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3)
#set(CMAKE_FIND_DEBUG_MODE FALSE)
if(NOT EMSCRIPTEN)
# There is no official assimp port for Emscripten, and we've had
# no time to experiment, so tests that use assimp are omitted from
# loadtests when building for the web.
find_package(assimp REQUIRED CONFIG REQUIRED)
endif()
# We use our own local copy of GL headers to ensure we have glcorearb.h.
set( OPENGL_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/other_include )
if(APPLE AND IOS)
if( CMAKE_OSX_ARCHITECTURES )
list(LENGTH CMAKE_OSX_ARCHITECTURES archs_len)
list(GET CMAKE_OSX_ARCHITECTURES 0 arch0)
if( ${archs_len} GREATER 1 OR (NOT ${arch0} STREQUAL "arm64") )
message(FATAL_ERROR "iOS loadtests only supported on arm64."
" Please disable KTX_FEATURE_LOADTEST_APPS"
" or change CMAKE_OSX_ARCHITECTURES.")
endif()
endif()
# Find Frameworks
find_library(AudioToolbox_LIBRARY AudioToolbox)
find_library(AVFoundation_LIBRARY AVFoundation)
find_library(CoreAudio_LIBRARY CoreAudio)
find_library(CoreBluetooth_LIBRARY CoreBluetooth)
find_library(CoreGraphics_LIBRARY CoreGraphics)
find_library(CoreHaptics_LIBRARY CoreHaptics)
find_library(CoreMotion_LIBRARY CoreMotion)
find_library(Foundation_LIBRARY Foundation)
find_library(GameController_LIBRARY GameController)
find_library(IOSurface_LIBRARY IOSurface)
if(${KTX_FEATURE_LOADTEST_APPS} MATCHES "Vulkan")
find_library(Metal_LIBRARY Metal)
endif()
if(${KTX_FEATURE_LOADTEST_APPS} MATCHES "OpenGL")
find_library(OpenGLES_LIBRARY OpenGLES)
endif()
find_library(QuartzCore_LIBRARY QuartzCore)
find_library(UIKit_LIBRARY UIKit)
endif()
function( ensure_runtime_dependencies_windows target )
# Custom copy commands to ensure all dependencies (testimages,
# shaders, models) are in correct location relative to executable.
if(${target} MATCHES "vkloadtests")
add_custom_command( TARGET ${target} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_BINARY_DIR}/shaders" "$<TARGET_FILE_DIR:${target}>/resources"
COMMENT "Copy shaders to build destination"
)
endif()
add_custom_command( TARGET ${target} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory "${PROJECT_SOURCE_DIR}/tests/testimages" "$<TARGET_FILE_DIR:${target}>/resources"
COMMENT "Copy testimages to build destination"
)
add_custom_command( TARGET ${target} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory "${PROJECT_SOURCE_DIR}/tests/loadtests/common/models" "$<TARGET_FILE_DIR:${target}>/resources"
COMMENT "Copy models to build destination"
)
endfunction()
add_library( appfwSDL STATIC
appfwSDL/AppBaseSDL.cpp
appfwSDL/AppBaseSDL.h
appfwSDL/main.cpp
common/LoadTestSample.cpp
common/LoadTestSample.h
common/ltexceptions.h
common/SwipeDetector.cpp
common/SwipeDetector.h
common/disable_glm_warnings.h
common/reenable_warnings.h
common/vecmath.hpp
geom/cube_data.h
geom/cube.h
geom/frame.h
geom/quad.h
${PROJECT_SOURCE_DIR}/external/SDL_gesture/SDL_gesture.h
)
target_compile_features(appfwSDL PUBLIC c_std_99 cxx_std_17)
if(EMSCRIPTEN)
target_compile_options( appfwSDL PUBLIC
"SHELL:--use-port=sdl3"
)
endif()
set_target_properties(appfwSDL PROPERTIES
CXX_VISIBILITY_PRESET ${STATIC_APP_LIB_SYMBOL_VISIBILITY}
)
target_include_directories(
appfwSDL
PUBLIC
appfwSDL
$<TARGET_PROPERTY:ktx,INTERFACE_INCLUDE_DIRECTORIES>
${PROJECT_SOURCE_DIR}/utils
${PROJECT_SOURCE_DIR}/external/SDL_gesture
common
geom
)
target_include_directories(
appfwSDL
SYSTEM AFTER PUBLIC
SDL3::Headers
${PROJECT_SOURCE_DIR}/other_include # For glm
)
target_link_libraries(
appfwSDL
PUBLIC
SDL3::SDL3
)
if(${KTX_FEATURE_LOADTEST_APPS} MATCHES "OpenGL")
add_library( GLAppSDL STATIC
appfwSDL/GLAppSDL.cpp
appfwSDL/GLAppSDL.h
glloadtests/GLLoadTests.cpp
glloadtests/GLLoadTests.h
)
if(EMSCRIPTEN)
target_compile_options( GLAppSDL PUBLIC
"SHELL:-s DISABLE_EXCEPTION_CATCHING=0"
"SHELL:--use-port=sdl3"
)
endif()
target_link_libraries(GLAppSDL appfwSDL)
target_include_directories(
GLAppSDL
PUBLIC
$<TARGET_PROPERTY:appfwSDL,INCLUDE_DIRECTORIES>
glloadtests
glloadtests/utils
)
# The above appfwSDL include brings with it the
# INTERFACE_SYSTEM_INCLUDE_DIRECTORIES.
target_include_directories(
GLAppSDL
SYSTEM BEFORE PUBLIC
$<IF:$<BOOL:${WIN32}>,${GLEW_INCLUDE_DIR},${OPENGL_INCLUDE_DIR}>
#$<TARGET_PROPERTY:appfwSDL,INTERFACE_SYSTEM_INCLUDE_DIRECTORIES>
)
set_target_properties(GLAppSDL PROPERTIES
CXX_VISIBILITY_PRESET ${STATIC_APP_LIB_SYMBOL_VISIBILITY}
)
endif()
set( LOAD_TEST_COMMON_LIBS )
if(APPLE)
set( EXE_FLAG MACOSX_BUNDLE )
set( KTX_APP_ICON_BASENAME ktx_app)
set( KTX_DOC_ICON_BASENAME ktx_document)
set( KTX_APP_ICON ${KTX_APP_ICON_BASENAME}.icns)
set( KTX_DOC_ICON ${KTX_DOC_ICON_BASENAME}.icns)
# On iOS an icon is a directory of images not a single file.
# BASENAME is the name of the directory in the common .xcassets directory.
# Assets are copied to the bundle in the build target setup.
set( KTX_APP_ICON_PATH ${PROJECT_SOURCE_DIR}/icons/mac/${KTX_APP_ICON} )
set( KTX_DOC_ICON_PATH ${PROJECT_SOURCE_DIR}/icons/mac/${KTX_DOC_ICON} )
cmake_print_variables(assimp_LIBRARIES)
if(IOS)
set( LOAD_TEST_COMMON_LIBS
assimp::assimp
)
else()
set( LOAD_TEST_COMMON_LIBS
assimp::assimp
)
endif()
elseif(LINUX)
set( KTX_APP_ICON_PATH ${PROJECT_SOURCE_DIR}/icons/linux/ktx_app.svg )
set( LOAD_TEST_COMMON_LIBS
assimp::assimp
#${SDL3_LIBRARIES}
SDL3::SDL3
)
elseif(WIN32)
set( EXE_FLAG WIN32 )
set( KTX_APP_ICON_PATH ${PROJECT_SOURCE_DIR}/icons/win/ktx_app.ico )
set( LOAD_TEST_COMMON_LIBS
assimp::assimp
)
endif()
set( LOAD_TEST_COLLADA_MODELS
"teapot.dae"
)
list(TRANSFORM LOAD_TEST_COLLADA_MODELS PREPEND "${CMAKE_CURRENT_SOURCE_DIR}/common/models/")
# An Xcode (or possibly CMake) update in late 2024 or early 2025 started
# compressing .dae files when copying them to bundle resources. This piece
# of undocumented magic makes it just copy. The Xcode GUI presents an option
# menu on the file with "Copy" and "Compress" in the project settings browser.
# This attribute sets it to "Copy."
set_source_files_properties( ${LOAD_TEST_COLLADA_MODELS}
PROPERTIES
XCODE_FILE_ATTRIBUTES "--decompress"
)
set( LOAD_TEST_OBJ_MODELS
"cube.obj"
"sphere.obj"
"torusknot.obj"
)
list(TRANSFORM LOAD_TEST_OBJ_MODELS PREPEND "${CMAKE_CURRENT_SOURCE_DIR}/common/models/")
# Hack to prevent *.obj 3D files being mistaken as linkable obj files
set_source_files_properties( ${LOAD_TEST_OBJ_MODELS}
PROPERTIES HEADER_FILE_ONLY TRUE
)
set( LOAD_TEST_COMMON_MODELS
${LOAD_TEST_COLLADA_MODELS}
${LOAD_TEST_OBJ_MODELS}
)
# A custom loadtest_models target does not work as configure fails at
# an install_target that uses this with an error "the target is not
# an executable, library or module."
#add_custom_target( loadtest_models
# SOURCES ${LOAD_TEST_COMMON_MODELS}
#)
set( LOAD_TEST_COMMON_RESOURCE_FILES
${KTX_APP_ICON_PATH}
${KTX_DOC_ICON_PATH}
${LOAD_TEST_COMMON_MODELS}
)
if(LINUX)
set( LOAD_TEST_DESTROOT "/opt" )
endif()
if(${KTX_FEATURE_LOADTEST_APPS} MATCHES "OpenGL")
include(glloadtests.cmake)
endif()
if(${KTX_FEATURE_LOADTEST_APPS} MATCHES "Vulkan")
include(vkloadtests.cmake)
endif()
+138
View File
@@ -0,0 +1,138 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/* $Id: f63e0a9e6eed51ed84a8eea1eff0708c8a6af22b $ */
/*
* Copyright 2015-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief Base class for SDL applications.
*/
#include "AppBaseSDL.h"
#include <iomanip>
#include <sstream>
#include <iostream>
bool
AppBaseSDL::initialize(Args& /*args*/)
{
const char* basePath = SDL_GetBasePath();
// SDL_GetBasePath returns directory of the app, except for
// iOS & macOS app bundles where it returns the Resources
// directory.
if (basePath == NULL)
basePath = SDL_strdup("./");
sBasePath = basePath;
#if SDL_PLATFORM_LINUX
// TODO figure out best way to handle these resources
sBasePath += "../resources/";
#endif
#if SDL_PLATFORM_WINDOWS
// Ditto
sBasePath += "resources/";
#endif
return true;
}
void
AppBaseSDL::initializeFPSTimer()
{
lastFrameTime = 0;
fpsCounter.numFrames = 0;
fpsCounter.lastFPS = 0;
startTicks = fpsCounter.startTicks = SDL_GetPerformanceCounter();
}
void
AppBaseSDL::finalize() {
}
bool
AppBaseSDL::doEvent(SDL_Event* event)
{
switch (event->type) {
case SDL_EVENT_QUIT:
finalize();
exit(0);
}
return true;
}
void
AppBaseSDL::onFPSUpdate()
{
}
// Protected, non-virtual
void
AppBaseSDL::drawFrame()
{
ticks_t ticks = SDL_GetPerformanceCounter();
Uint64 tps = SDL_GetPerformanceFrequency();
uint32_t msTicksSinceStart = (uint32_t)((ticks - startTicks) * 1000 / tps);
drawFrame(msTicksSinceStart);
ticks_t endTicks = SDL_GetPerformanceCounter();
lastFrameTime = (1000.0f * (endTicks - ticks)) / tps;
fpsCounter.numFrames++;
if (endTicks - fpsCounter.startTicks > tps) {
fpsCounter.lastFPS = (float)(fpsCounter.numFrames * tps)
/ (endTicks - fpsCounter.startTicks);
onFPSUpdate(); // Notify listeners that fps value has been updated.
fpsCounter.startTicks = endTicks;
fpsCounter.numFrames = 0;
}
}
//----------------------------------------------------------------------
// Window title and text overlay functions
//----------------------------------------------------------------------
void
AppBaseSDL::setAppTitle(const char* const szExtra)
{
appTitle = name();
if (szExtra != NULL && szExtra[0] != '\0') {
appTitle += ": ";
appTitle += szExtra;
}
setWindowTitle();
}
void
AppBaseSDL::setWindowTitle()
{
std::stringstream ss;
std::string wt;
ss << std::fixed << std::setprecision(2)
<< lastFrameTime << "ms (" << fpsCounter.lastFPS << " fps)" << " ";
wt = ss.str();
wt += appTitle;
SDL_SetWindowTitle(pswMainWindow, wt.c_str());
}
const char* appName()
{
return theApp->name();
}
+85
View File
@@ -0,0 +1,85 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
#ifndef APP_BASE_SDL_H_1456211087
#define APP_BASE_SDL_H_1456211087
/* $Id: f63e0a9e6eed51ed84a8eea1eff0708c8a6af22b $ */
/*
* Copyright 2015-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief Declarations for App framework using SDL.
*/
#include <SDL3/SDL.h>
#include <string>
#include <vector>
class AppBaseSDL {
public:
typedef Uint64 ticks_t;
typedef std::vector<std::string> Args;
AppBaseSDL(const char* const name) : szName(name), appTitle(name) { }
virtual bool initialize(Args& args);
virtual void finalize();
// Ticks in milliseconds since start.
virtual void drawFrame(uint32_t) { }
// When used with SDL_SetEventWatch, return value is ignored. When used
// with SDL_SetEventFilter, 1 causes event to be added to SDL's internal
// event queue, 0 causes it to be dropped.
virtual bool doEvent(SDL_Event* event);
virtual void onFPSUpdate();
virtual SDL_Window* getMainWindow() { return pswMainWindow; }
void drawFrame();
void initializeFPSTimer();
const char* name() { return szName; }
const std::string getAssetPath() { return sBasePath; }
// Sets title to be used on window title bar. Content of szExtra ia
// appended to the app name.
virtual void setAppTitle(const char* const szExtra);
static bool onEvent(void* userdata, SDL_Event* event) {
return ((AppBaseSDL *)userdata)->doEvent(event);
}
static void onDrawFrame(void* userdata) {
((AppBaseSDL *)userdata)->drawFrame();
}
protected:
// Sets text on window title bar. Fps value is preprended to appTitle.
virtual void setWindowTitle();
ticks_t startTicks;
float lastFrameTime; // ms
struct fpsCounter {
ticks_t startTicks;
int numFrames;
float lastFPS;
} fpsCounter;
SDL_Window* pswMainWindow;
const char* const szName;
std::string appTitle;
std::string sBasePath;
};
extern class AppBaseSDL* theApp;
#endif /* APP_BASE_SDL_H_1456211087 */
+330
View File
@@ -0,0 +1,330 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/* $Id: ac63511da134f2c25a9e1da86a36bc27b6198ae3 $ */
/*
* Copyright 2015-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief GLAppSDL app class.
*/
#if defined(_WIN32)
#if _MSC_VER < 1900
#define snprintf _snprintf
#endif
#define _CRT_SECURE_NO_WARNINGS
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#include "GL/glew.h"
#include "SDL3/SDL_loadso.h"
#else
#define GL_GLEXT_PROTOTYPES 1
#include "GL/glcorearb.h" // for glEnable and FRAMEBUFFER_RGB
#endif
#include <stdio.h>
#include <iomanip>
#include <sstream>
#include "GLAppSDL.h"
#if SDL_PLATFORM_WINDOWS
void setWindowsIcon(SDL_Window *sdlWindow);
#endif
bool
GLAppSDL::initialize(Args& args)
{
if (!AppBaseSDL::initialize(args))
return false;
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profile);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, majorVersion);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minorVersion);
// On SDL3 this defaults to 8. On SDL2 it was 0.
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
#if !defined(EMSCRIPTEN)
if (majorVersion >= 3)
SDL_GL_SetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, 1);
#endif
#if defined(DEBUG) && !defined(EMSCRIPTEN)
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
#endif
if (profile == SDL_GL_CONTEXT_PROFILE_ES) {
#if 0
int numVideoDrivers = SDL_GetNumVideoDrivers();
int i;
const char** drivers;
drivers = (const char**)SDL_malloc(sizeof(const char*) * numVideoDrivers);
for (i = 0; i < numVideoDrivers; i++) {
drivers[i] = SDL_GetVideoDriver(i);
}
#endif
// Only the indicated platforms pay attention to these hints
// but they could be set on any platform.
#if SDL_PLATFORM_WINDOWS || SDL_PLATFORM_LINUX
SDL_SetHint(SDL_HINT_OPENGL_ES_DRIVER, "1");
#endif
#if SDL_PLATFORM_WINDOWS
// If using ANGLE copied from Chrome should set to "d3dcompiler_46.dll"
// Should set value via compiler -D definition from gyp file.
SDL_SetHint(SDL_HINT_VIDEO_WIN_D3DCOMPILER, "none");
#endif
}
SDL_SetHint(SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK, "1");
// CAUTION: Setting this to 0 (the default) on macOS causes loss of all touch events
// from a trackpad not just those corresponding to mouse clicks.
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "1");
#if 0
const char* mt = SDL_GetHint(SDL_HINT_MOUSE_TOUCH_EVENTS);
SDL_Log("MOUSE_TOUCH_EVENTS = %s", mt);
const char* tm = SDL_GetHint(SDL_HINT_TOUCH_MOUSE_EVENTS);
SDL_Log("TOUCH_MOUSE_EVENTS = %s", tm);
const char* tto = SDL_GetHint(SDL_HINT_TRACKPAD_IS_TOUCH_ONLY);
SDL_Log("TRACKPAD_IS_TOUCH_ONLY = %s", tto);
#endif
pswMainWindow = SDL_CreateWindow(
szName,
w_width, w_height,
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE
);
if (pswMainWindow == NULL) {
(void)SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, szName, SDL_GetError(), NULL);
return false;
}
#if SDL_PLATFORM_WINDOWS
// Set the application's own icon in place of the Windows default set by SDL.
// Needs to be done here to avoid change being visible.
setWindowsIcon(pswMainWindow);
#endif
sgcGLContext = SDL_GL_CreateContext(pswMainWindow);
// Work around bug in SDL. It returns a 2.x context when 3.x is requested.
// It does though internally record an error.
const char* error = SDL_GetError();
if (sgcGLContext == NULL
|| (error[0] != '\0'
&& majorVersion >= 3
&& (profile == SDL_GL_CONTEXT_PROFILE_CORE
|| profile == SDL_GL_CONTEXT_PROFILE_COMPATIBILITY))
) {
(void)SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, szName, SDL_GetError(), NULL);
return false;
}
#if SDL_PLATFORM_WINDOWS
if (profile != SDL_GL_CONTEXT_PROFILE_ES)
{
// No choice but to use GLEW for GL on Windows; there is no .lib with static
// bindings. For ES we use one of the hardware vendor SDKs all of which have
// static bindings.
// TODO: Figure out how to support {GLX,WGL}_EXT_create_context_es2_profile
// were there are no static bindings. Need a GLEW equivalent for ES and
// different compile options. Perhaps can borrow function loading stuff
// from SDL's testgles2.c.
// So one build of this library can be linked in to applications using GLEW and
// applications not using GLEW, do not call any GLEW functions directly.
// Call via queried function pointers.
SDL_SharedObject* glewdll;
#if defined(_DEBUG)
glewdll = SDL_LoadObject("glew32d.dll");
// KTX-Software repo only contains non-debug library for x64 hence this.
if (glewdll == NULL) {
glewdll = SDL_LoadObject("glew32.dll");
}
#else
glewdll = SDL_LoadObject("glew32.dll");
#endif
if (glewdll == NULL) {
std::string sName(szName);
(void)SDL_ShowSimpleMessageBox(
SDL_MESSAGEBOX_ERROR,
szName,
SDL_GetError(),
NULL);
return false;
}
typedef GLenum(GLEWAPIENTRY PFNGLEWINIT)(void);
typedef const GLubyte * GLEWAPIENTRY PFNGLEWGETERRORSTRING(GLenum error);
PFNGLEWINIT* pGlewInit;
PFNGLEWGETERRORSTRING* pGlewGetErrorString = nullptr;
bool loadError = true;
#define STR(s) #s
#if defined(_M_IX86)
/* Win32 GLEW uses __stdcall. */
#define DNAMESTR(x,n) STR(_##x##@##n)
#else
/* x64 uses __cdecl. */
#define DNAMESTR(x,n) STR(x)
#endif
pGlewInit = (PFNGLEWINIT*)SDL_LoadFunction(glewdll, DNAMESTR(glewInit,0));
if (pGlewInit != NULL) {
pGlewGetErrorString = (PFNGLEWGETERRORSTRING*)SDL_LoadFunction(
glewdll, DNAMESTR(glewGetErrorString,4));
if (pGlewGetErrorString != NULL) {
loadError = false;
}
}
if (loadError) {
std::string sName(szName);
(void)SDL_ShowSimpleMessageBox(
SDL_MESSAGEBOX_ERROR,
szName,
SDL_GetError(),
NULL);
return false;
}
int iResult = pGlewInit();
if (iResult != GLEW_OK) {
std::string sName(szName);
(void)SDL_ShowSimpleMessageBox(
SDL_MESSAGEBOX_ERROR,
szName,
(const char*)pGlewGetErrorString(iResult),
NULL);
return false;
}
}
#endif
int srgb;
SDL_GL_GetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, &srgb);
if (srgb && profile != SDL_GL_CONTEXT_PROFILE_ES)
glEnable(GL_FRAMEBUFFER_SRGB);
// In case the window is created with a different size than specified.
int actualWidth, actualHeight;
SDL_GetWindowSizeInPixels(pswMainWindow, &actualWidth, &actualHeight);
resizeWindow(actualWidth, actualHeight);
initializeFPSTimer();
return true;
}
void
GLAppSDL::finalize()
{
SDL_GL_DestroyContext(sgcGLContext);
}
bool
GLAppSDL::doEvent(SDL_Event* event)
{
switch (event->type) {
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
resizeWindow(event->window.data1, event->window.data2);
return 0;
break;
}
return AppBaseSDL::doEvent(event);
}
void
GLAppSDL::drawFrame(uint32_t /*msTicks*/)
{
SDL_GL_SwapWindow(pswMainWindow);
}
void
GLAppSDL::windowResized()
{
// Derived class can override as necessary.
}
void
GLAppSDL::resizeWindow(int width, int height)
{
w_width = width;
w_height = height;
windowResized();
}
void
GLAppSDL::onFPSUpdate()
{
// Using onFPSUpdate avoids rewriting the title every frame.
setWindowTitle();
}
#if 0
void
GLAppSDL::setAppTitle(const char* const szExtra)
{
appTitle = name();
if (szExtra != NULL && szExtra[0] != '\0') {
appTitle += ": ";
appTitle += szExtra;
}
setWindowTitle();
}
void
GLAppSDL::setWindowTitle(const char* const szExtra)
{
std::stringstream ss;
ss << std::fixed << std::setprecision(2)
<< lastFrameTime << "ms (" << fpsCounter.lastFPS << " fps)"
<< " - " << szName;
if (szExtra != NULL && szExtra[0] != '\0') {
ss << ": " << szExtra;
}
SDL_SetWindowTitle(pswMainWindow, ss.str().c_str());
}
void
GLAppSDL::setWindowTitle()
{
SDL_SetWindowTitle(pswMainWindow, appTitle.c_str());
}
#endif
#if SDL_PLATFORM_WINDOWS
// Windows specific code to use icon in module
void
setWindowsIcon(SDL_Window *sdlWindow) {
HINSTANCE handle = ::GetModuleHandle(nullptr);
// Identify icon by name rather than IDI_ macro to avoid having to
// include application's resource.h.
HICON icon = ::LoadIcon(handle, "MAIN_ICON");// MAKEINTRESOURCE(IDI_ICON1));
if (icon != nullptr){
HWND hwnd = (HWND)SDL_GetPointerProperty(SDL_GetWindowProperties(sdlWindow), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);
if (hwnd) {
::SetClassLongPtr(hwnd, GCLP_HICON, reinterpret_cast<LONG_PTR>(icon));
}
}
}
#endif
// }
+59
View File
@@ -0,0 +1,59 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
#ifndef GL_APP_SDL_H_1456211188
#define GL_APP_SDL_H_1456211188
/* $Id: ac63511da134f2c25a9e1da86a36bc27b6198ae3 $ */
/*
* Copyright 2015-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief Declaration of GLAppSDL base class for GL apps.
*/
#include "AppBaseSDL.h"
class GLAppSDL : public AppBaseSDL {
public:
GLAppSDL(const char* const name,
int width, int height,
const SDL_GLProfile profile,
const int majorVersion,
const int minorVersion)
: AppBaseSDL(name),
profile(profile),
majorVersion(majorVersion), minorVersion(minorVersion)
{
appTitle = name;
w_width = width;
w_height = height;
};
virtual bool doEvent(SDL_Event* event);
virtual void drawFrame(uint32_t msTicks);
virtual void finalize();
virtual bool initialize(Args& args);
virtual void onFPSUpdate();
virtual void resizeWindow(int width, int height);
virtual void windowResized();
protected:
SDL_GLContext sgcGLContext;
int w_width;
int w_height;
const SDL_GLProfile profile;
const int majorVersion;
const int minorVersion;
};
#endif /* GL_APP_SDL_H_1456211188 */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,172 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
#ifndef VULKAN_APP_SDL_H_1456211188
#define VULKAN_APP_SDL_H_1456211188
/*
* Copyright 2017-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
#include <array>
#include <cstring>
#include <new>
#include <vector>
#define VK_ENABLE_BETA_EXTENSIONS 1
#include <vulkan/vulkan.hpp>
#include <SDL3/SDL_vulkan.h> // Must be after vulkan.hpp.
#include "AppBaseSDL.h"
#include "VulkanContext.h"
#include "VulkanSwapchain.h"
#include "vulkantextoverlay.hpp"
#include "unused.h"
class VulkanAppSDL : public AppBaseSDL {
public:
VulkanAppSDL(const char* const name,
int width, int height,
const uint32_t version,
bool enableTextOverlay)
: AppBaseSDL(name), w_width(width), w_height(height),
subOptimalPresentWarned(false), validate(false),
vkVersion(version),
enableTextOverlay(enableTextOverlay),
textOverlay(nullptr)
{
// The overridden new below will zero the storage. Thus
// we avoid a long list of initializers.
appTitle = name;
};
virtual ~VulkanAppSDL();
virtual bool doEvent(SDL_Event* event);
virtual void drawFrame(uint32_t msTicks);
virtual void finalize();
virtual bool initialize(Args& args);
virtual void onFPSUpdate();
virtual void resizeWindow(int width, int height);
virtual void windowResized();
static void* operator new(size_t size) {
void* storage = ::operator new(size);
memset(storage, 0, size);
return storage;
}
static void operator delete(void* storage, size_t) {
::operator delete(storage);
}
void updateTextOverlay();
// Called when the text overlay is updating
// Can be overridden in derived class to add custom text to the overlay
virtual void getOverlayText(float yOffset);
protected:
bool createDevice();
bool createInstance();
bool createPipelineCache();
bool createSemaphores();
bool createSurface();
bool createSwapchain();
bool findGpu();
void flushInitialCommands();
bool initializeVulkan();
bool prepareColorBuffers();
bool prepareCommandBuffers();
bool prepareDepthBuffer();
bool prepareDescriptorLayout();
void prepareFrame();
bool preparePresentCommandBuffers();
bool prepareRenderPass();
bool preparePipeline();
bool prepareDescriptorSet();
bool prepareFramebuffers();
void prepareTextOverlay();
void submitFrame();
bool setupDebugReporting();
enum stencilRequirement { eNoStencil = 0, eStencil = 1 };
enum depthRequirement { e16bits = 0, e24bits = 1, e32bits = 2 };
bool getSupportedDepthFormat(vk::PhysicalDevice gpu,
stencilRequirement requiredStencil,
depthRequirement requiredDepth,
vk::ImageTiling tiling,
vk::Format& pFormat,
vk::ImageAspectFlags& pAspectMask);
// Sets text on window title bar.
void setWindowTitle();
void setImageLayout(VkImage image, VkImageAspectFlags aspectMask,
VkImageLayout old_image_layout,
VkImageLayout new_image_layout,
VkAccessFlags srcAccessMask);
VKAPI_ATTR VkBool32 VKAPI_CALL
debugFunc(VkFlags msgFlags, VkDebugReportObjectTypeEXT objType,
uint64_t srcObject, size_t location, int32_t msgCode,
const char *pLayerPrefix, const char *pMsg);
std::string& wrapText(std::string& source, size_t width = 70,
const std::string& whitespace = " \t\r");
uint32_t showDebugReport(uint32_t mbFlags, const std::string title,
std::string message, bool enableAbort);
static bool checkLayers(uint32_t nameCount, const char **names,
uint32_t layerCount, VkLayerProperties *layers);
static VKAPI_ATTR VkBool32 VKAPI_CALL
debugFunc(VkFlags msgFlags, VkDebugReportObjectTypeEXT objType,
uint64_t srcObject, size_t location, int32_t msgCode,
const char *pLayerPrefix, const char *pMsg, void *pUserData);
bool prepared = false;
// Set true if want presents v-sync'ed.
bool enableVSync = false;
uint32_t w_width;
uint32_t w_height;
bool subOptimalPresentWarned;
bool validate;
std::vector<const char*> extensionNames;
std::vector<const char*> deviceValidationLayers;
VkCommandBuffer setupCmdBuffer;
VkSurfaceKHR vsSurface;
VulkanContext vkctx;
// Index of active framebuffer.
uint32_t currentBuffer;
// Synchronization semaphores
struct {
// Swap chain image presentation
VkSemaphore presentComplete;
// Command buffer submission and execution
VkSemaphore renderComplete;
// Text overlay submission and execution
VkSemaphore textOverlayComplete;
} semaphores;
const uint32_t vkVersion;
// Saved for clean-up
std::vector<vk::ShaderModule> shaderModules;
bool enableTextOverlay = false;
VulkanTextOverlay *textOverlay;
// List of shader modules created (stored for cleanup)
VkDebugReportCallbackEXT msgCallback;
PFN_vkCreateDebugReportCallbackEXT pfnCreateDebugReportCallbackEXT;
PFN_vkDestroyDebugReportCallbackEXT pfnDestroyDebugReportCallbackEXT;
PFN_vkDebugReportMessageEXT pfnDebugReportMessageEXT;
};
#endif /* VULKAN_APP_SDL_H_1456211188 */
@@ -0,0 +1,363 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2017-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @class VulkanContext
* @~English
*
* @brief Class for holding and passing Vulkan context info to applications.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#include <exception>
#include <iomanip>
#include <sstream>
#include <SDL3/SDL_iostream.h>
#include "VulkanContext.h"
// Until exceptions are used everywhere...
#include "vulkancheckres.h"
#include "ltexceptions.h"
#include "unused.h"
vk::CommandBuffer
VulkanContext::createCommandBuffer(vk::CommandBufferLevel level, bool begin)
{
vk::CommandBuffer cmdBuffer;
vk::CommandBufferAllocateInfo cmdBufAllocateInfo(
commandPool,
level,
1);
vk::Result res
= device.allocateCommandBuffers(&cmdBufAllocateInfo, &cmdBuffer);
if (res != vk::Result::eSuccess) {
throw bad_vulkan_alloc((int)res, "device.allocateCommandBuffers");
}
// If requested, also start the new command buffer
if (begin)
{
vk::CommandBufferBeginInfo cmdBufInfo;
res = cmdBuffer.begin(&cmdBufInfo);
if (res != vk::Result::eSuccess) {
throw bad_vulkan_alloc((int)res, "cmdBuffer.begin");
}
}
return cmdBuffer;
}
void
VulkanContext::flushCommandBuffer(vk::CommandBuffer& cmdBuffer,
bool free)
{
if (!cmdBuffer) {
return;
}
cmdBuffer.end();
vk::SubmitInfo submitInfo;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &cmdBuffer;
vk::Result res = queue.submit(1, &submitInfo, nullptr);
if (res != vk::Result::eSuccess) {
if (res == vk::Result::eErrorDeviceLost) {
throw std::runtime_error("Vulkan device lost.");
} else {
throw bad_vulkan_alloc((int)res, "queue.submit");
}
}
queue.waitIdle();
if (free) {
device.freeCommandBuffers(commandPool, 1, &cmdBuffer);
}
}
bool
VulkanContext::createDrawCommandBuffers()
{
VkCommandBufferAllocateInfo aInfo;
aInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
aInfo.pNext = NULL;
aInfo.commandPool = commandPool;
aInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
aInfo.commandBufferCount = 1;
drawCmdBuffers.resize(swapchain.imageCount);
for (uint32_t i = 0; i < swapchain.imageCount; i++) {
VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &aInfo,
&drawCmdBuffers[i]));
}
return true;
}
bool
VulkanContext::createPresentCommandBuffers()
{
VkCommandBufferAllocateInfo aInfo;
aInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
aInfo.pNext = NULL;
aInfo.commandPool = commandPool;
aInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
aInfo.commandBufferCount = 1;
prePresentCmdBuffers.resize(swapchain.imageCount);
postPresentCmdBuffers.resize(swapchain.imageCount);
for (uint32_t i = 0; i < swapchain.imageCount; i++) {
VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &aInfo,
&prePresentCmdBuffers[i]));
VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &aInfo,
&postPresentCmdBuffers[i]));
}
return true;
}
void
VulkanContext::destroyDrawCommandBuffers()
{
if (drawCmdBuffers.size() > 0) {
vkFreeCommandBuffers(device, commandPool,
static_cast<uint32_t>(drawCmdBuffers.size()),
drawCmdBuffers.data());
}
for (uint32_t i = 0; i < drawCmdBuffers.size(); ++i) {
drawCmdBuffers[i] = nullptr;
}
}
void
VulkanContext::destroyPresentCommandBuffers()
{
vkFreeCommandBuffers(device, commandPool,
static_cast<uint32_t>(drawCmdBuffers.size()),
prePresentCmdBuffers.data());
vkFreeCommandBuffers(device, commandPool,
static_cast<uint32_t>(drawCmdBuffers.size()),
postPresentCmdBuffers.data());
}
bool
VulkanContext::checkDrawCommandBuffers()
{
for (auto& cmdBuffer : drawCmdBuffers)
{
if (cmdBuffer == VK_NULL_HANDLE)
{
return false;
}
}
return true;
}
bool
VulkanContext::createBuffer(vk::BufferUsageFlags usageFlags,
vk::MemoryPropertyFlags memoryPropertyFlags,
vk::DeviceSize size,
void * data,
vk::Buffer* buffer,
vk::DeviceMemory* memory)
{
vk::MemoryRequirements memReqs;
vk::MemoryAllocateInfo memAlloc(0, 0);
vk::BufferCreateInfo bufferCreateInfo({}, size, usageFlags);
vk::Result res = device.createBuffer(&bufferCreateInfo, nullptr, buffer);
if (res != vk::Result::eSuccess) {
return false;
}
device.getBufferMemoryRequirements(*buffer, &memReqs);
memAlloc.allocationSize = memReqs.size;
memAlloc.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits,
memoryPropertyFlags);
res = device.allocateMemory(&memAlloc, nullptr, memory);
if (res == vk::Result::eSuccess) {
if (data != nullptr)
{
void *mapped;
mapped = device.mapMemory(*memory, 0, size, {});
memcpy(mapped, data, (size_t)size);
device.unmapMemory(*memory);
}
device.bindBufferMemory(*buffer, *memory, 0);
return true;
} else
return false;
}
bool
VulkanContext::createBuffer(vk::BufferUsageFlags usage,
vk::DeviceSize size,
void* data,
vk::Buffer* buffer,
vk::DeviceMemory* memory)
{
return createBuffer(usage, vk::MemoryPropertyFlagBits::eHostVisible,
size, data, buffer, memory);
}
bool
VulkanContext::createBuffer(vk::BufferUsageFlags usage,
vk::DeviceSize size,
void* data,
vk::Buffer* buffer,
vk::DeviceMemory* memory,
vk::DescriptorBufferInfo* descriptor)
{
bool res = createBuffer(usage, size, data, buffer, memory);
if (res)
{
descriptor->offset = 0;
descriptor->buffer = *buffer;
descriptor->range = size;
return true;
}
else
{
return false;
}
}
bool
VulkanContext::createBuffer(vk::BufferUsageFlags usage,
vk::MemoryPropertyFlags memoryPropertyFlags,
vk::DeviceSize size,
void* data,
vk::Buffer* buffer,
vk::DeviceMemory* memory,
vk::DescriptorBufferInfo* descriptor)
{
bool res = createBuffer(usage, memoryPropertyFlags, size, data, buffer, memory);
if (res)
{
descriptor->offset = 0;
descriptor->buffer = *buffer;
descriptor->range = size;
return true;
}
else
{
return false;
}
}
bool
VulkanContext::getMemoryType(uint32_t typeBits,
vk::MemoryPropertyFlags requirementsMask,
uint32_t *typeIndex) const
{
// Search memtypes to find first index with desired properties
for (uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; i++) {
if ((typeBits & 1) == 1) {
// Type is available, does it match user properties?
if ((memoryProperties.memoryTypes[i].propertyFlags &
requirementsMask) == requirementsMask) {
*typeIndex = i;
return true;
}
}
typeBits >>= 1;
}
// No memory types matched, return failure
return false;
}
uint32_t
VulkanContext::getMemoryType(uint32_t typeBits,
vk::MemoryPropertyFlags requirementsMask) const
{
// Search memtypes to find first index with desired properties
for (uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; i++) {
if ((typeBits & 1) == 1) {
// Type is available, does it match user properties?
if ((memoryProperties.memoryTypes[i].propertyFlags &
requirementsMask) == requirementsMask) {
return i;
}
}
typeBits >>= 1;
}
// No memory types matched, return failure
// TODO throw error
return 0;
}
vk::PipelineShaderStageCreateInfo
VulkanContext::loadShader(std::string filename,
vk::ShaderStageFlagBits stage,
const char* const modname)
{
vk::PipelineShaderStageCreateInfo shaderStage({}, stage);
shaderStage.module = loadShader(filename);
shaderStage.pName = modname;
return shaderStage;
}
vk::ShaderModule
VulkanContext::loadShader(std::string filename)
{
size_t codeSize;
uint32_t* shaderCode;
shaderCode = readSpv(filename.c_str(), &codeSize);
vk::ShaderModule shaderModule;
vk::ShaderModuleCreateInfo moduleCreateInfo({}, codeSize, shaderCode);
vk::Result res
= device.createShaderModule(&moduleCreateInfo, NULL, &shaderModule);
if (res != vk::Result::eSuccess) {
throw bad_vulkan_alloc((int)res, "device.createShaderModule");
}
delete[] shaderCode;
assert(shaderModule);
return shaderModule;
}
uint32_t*
VulkanContext::readSpv(const char *filename, size_t *pSize) {
size_t size;
U_ASSERT_ONLY size_t retval;
uint32_t* shader_code;
SDL_IOStream* io = SDL_IOFromFile(filename, "rb");
if (!io) {
std::stringstream message;
// String returned by SDL_GetError() includes file name.
message << "Open of shader failed: " << SDL_GetError();
throw std::runtime_error(message.str());
}
size = (size_t)SDL_GetIOSize(io);
// Round-up to next 4-byte size.
shader_code = new uint32_t[(size + 3)/4];
retval = SDL_ReadIO(io, shader_code, size);
assert(retval == size);
*pSize = size;
SDL_CloseIO(io);
return shader_code;
}
@@ -0,0 +1,134 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
#ifndef VULKAN_TEXTURE_H_229895365400979164311947449304284143508
#define VULKAN_TEXTURE_H_229895365400979164311947449304284143508
/*
* Copyright 2017-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
#include <vector>
#define VK_ENABLE_BETA_EXTENSIONS 1
#include <vulkan/vulkan.hpp>
#include "VulkanSwapchain.h"
struct VulkanDepthBuffer {
VkFormat format;
VkImage image;
VkMemoryAllocateInfo memAlloc;
VkDeviceMemory mem;
VkImageView view;
};
struct VulkanContext {
vk::Instance instance;
vk::PhysicalDevice gpu;
vk::PhysicalDeviceFeatures gpuFeatures;
#if VK_KHR_portability_subset
vk::PhysicalDevicePortabilitySubsetFeaturesKHR gpuPortabilityFeatures;
#endif
vk::PhysicalDeviceProperties gpuProperties;
vk::PhysicalDeviceMemoryProperties memoryProperties;
vk::Device device;
vk::CommandPool commandPool;
vk::Queue queue;
bool gpuIsPortabilitySubsetDevice = false;
struct {
bool pvrtc = false;
bool astc_hdr = false;
bool astc_3d = false;
} enabledDeviceExtensions;
std::vector<VkCommandBuffer> drawCmdBuffers;
std::vector<VkCommandBuffer> postPresentCmdBuffers;
std::vector<VkCommandBuffer> prePresentCmdBuffers;
VkRenderPass renderPass;
// Pipeline stage flags for the submit info structure
const VkPipelineStageFlags submitPipelineStages =
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
// Contains command buffers and semaphores to be presented to the queue
VkSubmitInfo drawCmdSubmitInfo;
vk::DescriptorPool descriptorPool;
VkPipelineCache pipelineCache;
VulkanSwapchain swapchain;
// List of frame buffers (same as number of swap chain images)
std::vector<VkFramebuffer>framebuffers;
VulkanDepthBuffer depthBuffer;
// Create a new command buffer, opening it for command entry,
// if requested.
vk::CommandBuffer createCommandBuffer(vk::CommandBufferLevel level,
bool begin);
// End a command buffer, submit it to the queue and free, if requested.
// Note : Waits for the queue to become idle
void flushCommandBuffer(vk::CommandBuffer& commandBuffer, bool free);
// Create a command buffer for each image in the swap chain.
bool createDrawCommandBuffers();
bool createPresentCommandBuffers();
bool checkDrawCommandBuffers();
void destroyDrawCommandBuffers();
void destroyPresentCommandBuffers();
bool getMemoryType(uint32_t typeBits,
vk::MemoryPropertyFlags requirementsMask,
uint32_t *typeIndex) const;
uint32_t getMemoryType(uint32_t typeBits,
vk::MemoryPropertyFlags requirementsMask) const;
// Create a buffer, fill it with data (if != NULL) and bind buffer memory
bool createBuffer(
vk::BufferUsageFlags usageFlags,
vk::MemoryPropertyFlags memoryPropertyFlags,
vk::DeviceSize size,
void *data,
vk::Buffer *buffer,
vk::DeviceMemory *memory);
// This version always uses HOST_VISIBLE memory
bool createBuffer(
vk::BufferUsageFlags usage,
vk::DeviceSize size,
void *data,
vk::Buffer *buffer,
vk::DeviceMemory *memory);
// Overload that assigns buffer info to descriptor
bool createBuffer(
vk::BufferUsageFlags usage,
vk::DeviceSize size,
void* data,
vk::Buffer* buffer,
vk::DeviceMemory* memory,
vk::DescriptorBufferInfo* descriptor);
// Overload to pass memory property flags
bool createBuffer(
vk::BufferUsageFlags usage,
vk::MemoryPropertyFlags memoryPropertyFlags,
vk::DeviceSize size,
void* data,
vk::Buffer* buffer,
vk::DeviceMemory* memory,
vk::DescriptorBufferInfo* descriptor);
vk::PipelineShaderStageCreateInfo loadShader(std::string filename,
vk::ShaderStageFlagBits stage,
const char* const modname = "main");
vk::ShaderModule loadShader(std::string filename);
uint32_t* readSpv(const char *filename, size_t *pSize);
bool gpuSupportsSwizzle() {
#if VK_KHR_portability_subset
return !gpuIsPortabilitySubsetDevice
|| gpuPortabilityFeatures.imageViewFormatSwizzle;
#else
return true;
#endif
}
};
#endif /* VULKAN_TEXTURE_H_229895365400979164311947449304284143508 */
@@ -0,0 +1,434 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2017-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @class VulkanSwapchain
* @~English
*
* @brief Manage the swapchain for a Vulkan app.
*
* A swap chain is a collection of image buffers used for rendering
* The images can then be presented to the windowing system for display.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#include <stdlib.h>
#include <string>
#include <fstream>
#include <assert.h>
#include <stdio.h>
#include <vector>
#ifdef _WIN32
#include <windows.h>
#include <fcntl.h>
#include <io.h>
#else
#endif
#include "VulkanSwapchain.h"
#include <SDL3/SDL.h>
#include <SDL3/SDL_vulkan.h>
#include "AppBaseSDL.h"
#include "unused.h"
#define ERROR_RETURN(msg) \
(void)SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, theApp->name(), \
msg, NULL); \
return false;
#define GET_INSTANCE_PROC_ADDR(inst, entrypoint) \
{ \
pfn##entrypoint = \
(PFN_vk##entrypoint)vkGetInstanceProcAddr(inst, "vk"#entrypoint); \
if (pfn##entrypoint == NULL) { \
ERROR_RETURN("vkGetInstanceProcAddr: unable to find vk"#entrypoint); \
} \
}
#define GET_DEVICE_PROC_ADDR(device, entrypoint) \
{ \
pfn##entrypoint = \
(PFN_vk##entrypoint)vkGetDeviceProcAddr(device, "vk"#entrypoint); \
if (pfn##entrypoint == NULL) { \
ERROR_RETURN("vkGetDeviceProcAddr: unable to find vk"#entrypoint); \
} \
}
// Creates an os specific surface
// Tries to find a graphics and a present queue
bool
VulkanSwapchain::initSurface(SDL_Window* window)
{
U_ASSERT_ONLY VkResult err;
if (!SDL_Vulkan_CreateSurface(window, instance, nullptr, &surface)) {
std::string msg = "SDL_CreateVulkanSurface failed: ";
msg += SDL_GetError();
ERROR_RETURN(msg.c_str());
}
// Get available queue family properties
uint32_t queueCount;
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueCount, NULL);
assert(queueCount >= 1);
std::vector<VkQueueFamilyProperties> queueProps(queueCount);
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueCount,
queueProps.data());
// Iterate over the queues looking for ones which support presenting.
std::vector<VkBool32> supportsPresent(queueCount);
for (uint32_t i = 0; i < queueCount; i++) {
vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, i, surface,
&supportsPresent[i]);
}
// Search for a graphics- and present-capable queue.
uint32_t graphicsQueueIndex = UINT32_MAX;
uint32_t presentQueueIndex = UINT32_MAX;
for (uint32_t i = 0; i < queueCount; i++) {
if ((queueProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0)
{
if (graphicsQueueIndex == UINT32_MAX)
{
graphicsQueueIndex = i;
}
if (supportsPresent[i] == VK_TRUE)
{
graphicsQueueIndex = i;
presentQueueIndex = i;
break;
}
}
}
if (presentQueueIndex == UINT32_MAX) {
// If there's no queue that supports both present and graphics
// try to find a separate present queue
for (uint32_t i = 0; i < queueCount; ++i)
{
if (supportsPresent[i] == VK_TRUE)
{
presentQueueIndex = i;
break;
}
}
}
// Exit if either a graphics or a presenting queue hasn't been found
if (graphicsQueueIndex == UINT32_MAX
|| presentQueueIndex == UINT32_MAX)
{
ERROR_RETURN("Could not find a graphics or presenting queue!");
}
// TODO: Add support for separate graphics and presenting queue
if (graphicsQueueIndex != presentQueueIndex)
{
ERROR_RETURN("Separate graphics and present queues not yet supported!");
}
queueIndex = graphicsQueueIndex;
// Get list of supported surface formats
uint32_t formatCount;
err = vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface,
&formatCount, NULL);
assert(err == VK_SUCCESS);
assert(formatCount > 0);
std::vector<VkSurfaceFormatKHR> surfaceFormats(formatCount);
err = vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface,
&formatCount,
surfaceFormats.data());
assert(err == VK_SUCCESS);
// If the surface format list only includes one entry with
// VK_FORMAT_UNDEFINED, there is no preferred format.
// Assume VK_FORMAT_B8G8R8A8_RGB.
// TODO: Consider passing in desired format from app.
if ((formatCount == 1) && (surfaceFormats[0].format == VK_FORMAT_UNDEFINED))
{
colorFormat = VK_FORMAT_B8G8R8A8_SRGB;
}
else
{
assert(formatCount >= 1);
uint32_t i;
for (i = 0; i < formatCount; i++) {
if (surfaceFormats[i].format == VK_FORMAT_B8G8R8A8_SRGB) {
break;
}
}
if (i == formatCount) {
// Pick the first available, if no SRGB.
// FIXME probably should raise an error...
i = 0;
}
colorFormat = surfaceFormats[i].format;
}
colorSpace = surfaceFormats[0].colorSpace;
return true;
}
// Connect to the instance and device and get all required function pointers
bool
VulkanSwapchain::connectDevice(VkDevice targetDevice)
{
this->device = targetDevice;
#if USE_FUNCPTRS_FOR_KHR_EXTS
GET_DEVICE_PROC_ADDR(device, CreateSwapchainKHR);
GET_DEVICE_PROC_ADDR(device, DestroySwapchainKHR);
GET_DEVICE_PROC_ADDR(device, GetSwapchainImagesKHR);
GET_DEVICE_PROC_ADDR(device, AcquireNextImageKHR);
GET_DEVICE_PROC_ADDR(device, QueuePresentKHR);
#endif
return true;
}
// Connect to the instance and device and get all required function pointers
bool
VulkanSwapchain::connectInstance(VkInstance targetInstance,
VkPhysicalDevice targetPhysicalDevice)
{
this->instance = targetInstance;
this->physicalDevice = targetPhysicalDevice;
#if USE_FUNCPTRS_FOR_KHR_EXTS
GET_INSTANCE_PROC_ADDR(instance, GetPhysicalDeviceSurfaceSupportKHR);
GET_INSTANCE_PROC_ADDR(instance, GetPhysicalDeviceSurfaceCapabilitiesKHR);
GET_INSTANCE_PROC_ADDR(instance, GetPhysicalDeviceSurfaceFormatsKHR);
GET_INSTANCE_PROC_ADDR(instance, GetPhysicalDeviceSurfacePresentModesKHR);
#endif
return true;
}
// Create the swap chain and get images with given width and height
void
VulkanSwapchain::create(uint32_t *width, uint32_t *height,
bool vsync)
{
U_ASSERT_ONLY VkResult err;
VkSwapchainKHR oldSwapchain = swapchain;
// Get physical device surface properties and formats
VkSurfaceCapabilitiesKHR surfCaps;
err = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface,
&surfCaps);
assert(err == VK_SUCCESS);
// Get available present modes
uint32_t presentModeCount;
err = vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface,
&presentModeCount, NULL);
assert(err == VK_SUCCESS);
assert(presentModeCount > 0);
std::vector<VkPresentModeKHR> presentModes(presentModeCount);
err = vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface,
&presentModeCount,
presentModes.data());
assert(err == VK_SUCCESS);
VkExtent2D swapchainExtent = {};
// width and height are either both 0xFFFFFFFF, or both not 0xFFFFFFFF.
if (surfCaps.currentExtent.width == UINT32_MAX)
{
// If the surface size is undefined, the size is set to
// the size of the images requested.
swapchainExtent.width = *width;
swapchainExtent.height = *height;
}
else
{
swapchainExtent = surfCaps.currentExtent;
*width = surfCaps.currentExtent.width;
*height = surfCaps.currentExtent.height;
}
// Select a present mode for the swapchain
// The VK_PRESENT_MODE_FIFO_KHR mode must always be present as per spec
// This mode waits for the vertical blank ("v-sync")
VkPresentModeKHR swapchainPresentMode = VK_PRESENT_MODE_FIFO_KHR;
// If v-sync is not requested, try to find a mailbox mode if present
// It's the lowest latency non-tearing present mode available
if (!vsync)
{
for (size_t i = 0; i < presentModeCount; i++)
{
if (presentModes[i] == VK_PRESENT_MODE_MAILBOX_KHR)
{
swapchainPresentMode = VK_PRESENT_MODE_MAILBOX_KHR;
break;
}
if ((swapchainPresentMode != VK_PRESENT_MODE_MAILBOX_KHR)
&& (presentModes[i] == VK_PRESENT_MODE_IMMEDIATE_KHR))
{
swapchainPresentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
}
}
}
// Determine the number of images
uint32_t desiredNumberOfSwapchainImages = surfCaps.minImageCount + 1;
if ((surfCaps.maxImageCount > 0)
&& (desiredNumberOfSwapchainImages > surfCaps.maxImageCount))
{
desiredNumberOfSwapchainImages = surfCaps.maxImageCount;
}
VkSurfaceTransformFlagsKHR preTransform;
if (surfCaps.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) {
preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
} else {
preTransform = surfCaps.currentTransform;
}
VkSwapchainCreateInfoKHR swapchainCI = {};
swapchainCI.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
swapchainCI.pNext = NULL;
swapchainCI.surface = surface;
swapchainCI.minImageCount = desiredNumberOfSwapchainImages;
swapchainCI.imageFormat = colorFormat;
swapchainCI.imageColorSpace = colorSpace;
swapchainCI.imageExtent = { swapchainExtent.width, swapchainExtent.height };
swapchainCI.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
swapchainCI.preTransform = (VkSurfaceTransformFlagBitsKHR)preTransform;
swapchainCI.imageArrayLayers = 1;
swapchainCI.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
swapchainCI.queueFamilyIndexCount = 0;
swapchainCI.pQueueFamilyIndices = NULL;
swapchainCI.presentMode = swapchainPresentMode;
swapchainCI.oldSwapchain = oldSwapchain;
swapchainCI.clipped = true;
swapchainCI.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
err = vkCreateSwapchainKHR(device, &swapchainCI, nullptr, &swapchain);
assert(err == VK_SUCCESS);
// If an existing swap chain is re-created, destroy the old swap chain
// This also cleans up all the presentable images
if (oldSwapchain != VK_NULL_HANDLE)
{
for (uint32_t i = 0; i < imageCount; i++)
{
vkDestroyImageView(device, buffers[i].view, nullptr);
}
vkDestroySwapchainKHR(device, oldSwapchain, nullptr);
}
err = vkGetSwapchainImagesKHR(device, swapchain, &imageCount, NULL);
assert(err == VK_SUCCESS);
// Get the swap chain images
images.resize(imageCount);
err = vkGetSwapchainImagesKHR(device, swapchain,
&imageCount, images.data());
assert(err == VK_SUCCESS);
// Get the swap chain buffers containing the image and imageview
buffers.resize(imageCount);
for (uint32_t i = 0; i < imageCount; i++)
{
VkImageViewCreateInfo colorAttachmentView = {};
colorAttachmentView.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
colorAttachmentView.pNext = NULL;
colorAttachmentView.format = colorFormat;
colorAttachmentView.components = {
VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY
};
colorAttachmentView.subresourceRange.aspectMask
= VK_IMAGE_ASPECT_COLOR_BIT;
colorAttachmentView.subresourceRange.baseMipLevel = 0;
colorAttachmentView.subresourceRange.levelCount = 1;
colorAttachmentView.subresourceRange.baseArrayLayer = 0;
colorAttachmentView.subresourceRange.layerCount = 1;
colorAttachmentView.viewType = VK_IMAGE_VIEW_TYPE_2D;
colorAttachmentView.flags = 0;
buffers[i].image = images[i];
colorAttachmentView.image = buffers[i].image;
err = vkCreateImageView(device, &colorAttachmentView, nullptr,
&buffers[i].view);
assert(err == VK_SUCCESS);
}
}
// Acquires the next image in the swap chain
VkResult
VulkanSwapchain::acquireNextImage(VkSemaphore presentCompleteSemaphore,
uint32_t *currentBuffer)
{
return vkAcquireNextImageKHR(device, swapchain, UINT64_MAX,
presentCompleteSemaphore,
(VkFence)nullptr,
currentBuffer);
}
// Present the current image to the queue
VkResult
VulkanSwapchain::queuePresent(VkQueue queue, uint32_t currentBuffer)
{
VkPresentInfoKHR presentInfo = {};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.pNext = NULL;
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = &swapchain;
presentInfo.pImageIndices = &currentBuffer;
return vkQueuePresentKHR(queue, &presentInfo);
}
// Present the current image to the queue when semaphore signaled.
VkResult
VulkanSwapchain::queuePresent(VkQueue queue, uint32_t currentBuffer,
VkSemaphore waitSemaphore)
{
VkPresentInfoKHR presentInfo = {};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.pNext = NULL;
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = &swapchain;
presentInfo.pImageIndices = &currentBuffer;
if (waitSemaphore != VK_NULL_HANDLE)
{
presentInfo.pWaitSemaphores = &waitSemaphore;
presentInfo.waitSemaphoreCount = 1;
}
return vkQueuePresentKHR(queue, &presentInfo);
}
// Free all Vulkan resources used by the swap chain
void
VulkanSwapchain::cleanup()
{
for (uint32_t i = 0; i < imageCount; i++)
{
vkDestroyImageView(device, buffers[i].view, nullptr);
}
vkDestroySwapchainKHR(device, swapchain, nullptr);
vkDestroySurfaceKHR(instance, surface, nullptr);
}
@@ -0,0 +1,105 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2017-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <vector>
#include <vulkan/vulkan.h>
#if !defined(USE_FUNCPTRS_FOR_KHR_EXTS)
#define USE_FUNCPTRS_FOR_KHR_EXTS 0
#endif
typedef struct _SwapchainBuffers {
VkImage image;
VkImageView view;
} SwapchainBuffer;
class VulkanSwapchain
{
public:
VkFormat colorFormat;
VkColorSpaceKHR colorSpace;
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
uint32_t imageCount;
std::vector<VkImage> images;
std::vector<SwapchainBuffer> buffers;
// Index of the detected graphics- and present-capable device queue.
uint32_t queueIndex = UINT32_MAX;
// Creates an OS specific surface.
// Looks for a graphics and a present queue
bool initSurface(struct SDL_Window* window);
// Connect to device and get required device function pointers.
bool connectDevice(VkDevice device);
// Connect to instance and get required instance function pointers.
bool connectInstance(VkInstance instance,
VkPhysicalDevice physicalDevice);
// Create the swap chain and get images with given width and height
void create(uint32_t *width, uint32_t *height,
bool vsync = false);
// Acquires the next image in the swap chain
VkResult acquireNextImage(VkSemaphore presentCompleteSemaphore,
uint32_t *currentBuffer);
// Present the current image to the queue
VkResult queuePresent(VkQueue queue, uint32_t currentBuffer);
// Present the current image to the queue
VkResult queuePresent(VkQueue queue, uint32_t currentBuffer,
VkSemaphore waitSemaphore);
// Free all Vulkan resources used by the swap chain
void cleanup();
private:
VkInstance instance;
VkDevice device;
VkPhysicalDevice physicalDevice;
VkSurfaceKHR surface;
#if USE_FUNCPTRS_FOR_KHR_EXTS
PFN_vkGetPhysicalDeviceSurfaceSupportKHR
pfnGetPhysicalDeviceSurfaceSupportKHR;
PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR
pfnGetPhysicalDeviceSurfaceCapabilitiesKHR;
PFN_vkGetPhysicalDeviceSurfaceFormatsKHR
pfnGetPhysicalDeviceSurfaceFormatsKHR;
PFN_vkGetPhysicalDeviceSurfacePresentModesKHR
pfnGetPhysicalDeviceSurfacePresentModesKHR;
PFN_vkCreateSwapchainKHR pfnCreateSwapchainKHR;
PFN_vkDestroySwapchainKHR pfnDestroySwapchainKHR;
PFN_vkGetSwapchainImagesKHR pfnGetSwapchainImagesKHR;
PFN_vkAcquireNextImageKHR pfnAcquireNextImageKHR;
PFN_vkQueuePresentKHR pfnQueuePresentKHR;
#define vkGetPhysicalDeviceSurfaceSupportKHR \
pfnGetPhysicalDeviceSurfaceSupportKHR
#define vkGetPhysicalDeviceSurfaceCapabilitiesKHR \
pfnGetPhysicalDeviceSurfaceCapabilitiesKHR
#define vkGetPhysicalDeviceSurfaceFormatsKHR \
pfnGetPhysicalDeviceSurfaceFormatsKHR
#define vkGetPhysicalDeviceSurfacePresentModesKHR \
pfnGetPhysicalDeviceSurfacePresentModesKHR
#define vkCreateSwapchainKHR pfnCreateSwapchainKHR
#define vkDestroySwapchainKHR pfnDestroySwapchainKHR
#define vkGetSwapchainImagesKHR pfnGetSwapchainImagesKHR
#define vkAcquireNextImageKHR pfnAcquireNextImageKHR
#define vkQueuePresentKHR pfnQueuePresentKHR
#endif
};
@@ -0,0 +1,8 @@
<!-- Copyright 2024 Mark Callow -->
<!-- SPDX-License-Identifier: Apache-2.0 -->
# Vulkan ICD and Explicit Layer Manifest Files
These were copied from `macOS/share/vulkan` in the Vulkan SDK and have been
modified for inclusion in a macOS application bundle to cause the Vulkan loader
to find the libraries within the bundle.
@@ -0,0 +1,8 @@
{
"file_format_version": "1.0.0",
"ICD": {
"library_path": "../../../Frameworks/libMoltenVK.dylib",
"api_version": "1.2.0",
"is_portability_driver": true
}
}
@@ -0,0 +1,16 @@
// Copyright 2017-2020 Mark Callow
// SPDX-License-Identifier: Apache-2.0
#version 450 core
layout (location = 0) in vec2 inUV;
layout (binding = 0) uniform sampler2D samplerFont;
layout (location = 0) out vec4 outFragColor;
void main(void)
{
float color = texture(samplerFont, inUV).r;
outFragColor = vec4(vec3(color), 1.0);
}
@@ -0,0 +1,20 @@
// Copyright 2017-2020 Mark Callow
// SPDX-License-Identifier: Apache-2.0
#version 450 core
layout (location = 0) in vec2 inPos;
layout (location = 1) in vec2 inUV;
layout (location = 0) out vec2 outUV;
out gl_PerVertex
{
vec4 gl_Position;
};
void main(void)
{
gl_Position = vec4(inPos, 0.0, 1.0);
outUV = inUV;
}
@@ -0,0 +1,48 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
#ifndef VULKAN_CHECK_RES_H_1456211188
#define VULKAN_CHECK_RES_H_1456211188
/*
* Copyright 2017-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include <iomanip>
#include <sstream>
#include <SDL3/SDL_messagebox.h>
/**
* @internal
* @file
* @~English
*
* @brief Check result of a Vulkan command.
*
* Use for commands that will always succeed unless usage is invalid.
*/
#if defined(DEBUG)
#define VK_CHECK_RESULT(f) \
{ \
VkResult res = (f); \
if (res != VK_SUCCESS) \
{ \
std::stringstream msg; \
msg << "Fatal : VkResult is \"" << res << "\" in " << __FILE__ \
<< " at line " << __LINE__ << std::endl; \
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, \
"VkSample_02_cube_textured", \
msg.str().c_str(), \
NULL); \
assert(res == VK_SUCCESS); \
} \
}
#else
#define VK_CHECK_RESULT(f) (void)f
#endif
#endif
@@ -0,0 +1,288 @@
/*
* Vulkan examples debug wrapper
*
* Appendix for VK_EXT_Debug_Report can be found at https://github.com/KhronosGroup/Vulkan-Docs/blob/1.0-VK_EXT_debug_report/doc/specs/vulkan/appendices/debug_report.txt
*
* Copyright 2016 Sascha Willems - www.saschawillems.de
* SPDX-License-Identifier: MIT
*/
#include "vulkandebug.h"
#include <iostream>
#include "unused.h"
namespace vkDebug
{
int validationLayerCount = 1;
const char *validationLayerNames[] =
{
// This is a meta layer that enables all of the standard
// validation layers in the correct order :
// threading, parameter_validation, device_limits, object_tracker, image, core_validation, swapchain, and unique_objects
"VK_LAYER_LUNARG_standard_validation"
};
PFN_vkCreateDebugReportCallbackEXT CreateDebugReportCallback = VK_NULL_HANDLE;
PFN_vkDestroyDebugReportCallbackEXT DestroyDebugReportCallback = VK_NULL_HANDLE;
PFN_vkDebugReportMessageEXT dbgBreakCallback = VK_NULL_HANDLE;
VkDebugReportCallbackEXT msgCallback;
VkBool32 messageCallback(
VkDebugReportFlagsEXT flags,
VkDebugReportObjectTypeEXT /*objType*/,
uint64_t /*srcObject*/,
size_t /*location*/,
int32_t msgCode,
const char* pLayerPrefix,
const char* pMsg,
void* /*pUserData*/)
{
// Message text passed in by validation layer
std::string text(pMsg);
// Select prefix depending on flags passed to the callback
// Note that multiple flags may be set for a single validation message
std::string prefix("");
// Error that may result in undefined behaviour
if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT)
{
prefix += "ERROR:";
};
// Warnings may hint at unexpected / non-spec API usage
if (flags & VK_DEBUG_REPORT_WARNING_BIT_EXT)
{
prefix += "WARNING:";
};
// May indicate sub-optimal usage of the API
if (flags & VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT)
{
prefix += "PERFORMANCE:";
};
// Informal messages that may become handy during debugging
if (flags & VK_DEBUG_REPORT_INFORMATION_BIT_EXT)
{
prefix += "INFO:";
}
// Diagnostic info from the Vulkan loader and layers
// Usually not helpful in terms of API usage, but may help to debug layer and loader problems
if (flags & VK_DEBUG_REPORT_DEBUG_BIT_EXT)
{
prefix += "DEBUG:";
}
// Display message to default output (console if activated)
std::cout << prefix << " [" << pLayerPrefix << "] Code " << msgCode << " : " << pMsg << "\n";
fflush(stdout);
// The return value of this callback controls wether the Vulkan call that caused
// the validation message will be aborted or not
// We return VK_FALSE as we DON'T want Vulkan calls that cause a validation message
// (and return a VkResult) to abort
// If you instead want to have calls abort, pass in VK_TRUE and the function will
// return VK_ERROR_VALIDATION_FAILED_EXT
return VK_FALSE;
}
void setupDebugging(VkInstance instance, VkDebugReportFlagsEXT flags, VkDebugReportCallbackEXT callBack)
{
CreateDebugReportCallback = (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT");
DestroyDebugReportCallback = (PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT");
dbgBreakCallback = (PFN_vkDebugReportMessageEXT)vkGetInstanceProcAddr(instance, "vkDebugReportMessageEXT");
VkDebugReportCallbackCreateInfoEXT dbgCreateInfo = {};
dbgCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT;
dbgCreateInfo.pfnCallback = (PFN_vkDebugReportCallbackEXT)messageCallback;
dbgCreateInfo.flags = flags;
U_ASSERT_ONLY VkResult err = CreateDebugReportCallback(
instance,
&dbgCreateInfo,
nullptr,
(callBack != VK_NULL_HANDLE) ? &callBack : &msgCallback);
assert(!err);
(void)err; // Supress VC++ unused variable warning for Release config.
}
void freeDebugCallback(VkInstance instance)
{
if (msgCallback != VK_NULL_HANDLE)
{
DestroyDebugReportCallback(instance, msgCallback, nullptr);
}
}
namespace DebugMarker
{
bool active = false;
PFN_vkDebugMarkerSetObjectTagEXT pfnDebugMarkerSetObjectTag = VK_NULL_HANDLE;
PFN_vkDebugMarkerSetObjectNameEXT pfnDebugMarkerSetObjectName = VK_NULL_HANDLE;
PFN_vkCmdDebugMarkerBeginEXT pfnCmdDebugMarkerBegin = VK_NULL_HANDLE;
PFN_vkCmdDebugMarkerEndEXT pfnCmdDebugMarkerEnd = VK_NULL_HANDLE;
PFN_vkCmdDebugMarkerInsertEXT pfnCmdDebugMarkerInsert = VK_NULL_HANDLE;
void setup(VkDevice device)
{
pfnDebugMarkerSetObjectTag = (PFN_vkDebugMarkerSetObjectTagEXT)vkGetDeviceProcAddr(device, "vkDebugMarkerSetObjectTagEXT");
pfnDebugMarkerSetObjectName = (PFN_vkDebugMarkerSetObjectNameEXT)vkGetDeviceProcAddr(device, "vkDebugMarkerSetObjectNameEXT");
pfnCmdDebugMarkerBegin = (PFN_vkCmdDebugMarkerBeginEXT)vkGetDeviceProcAddr(device, "vkCmdDebugMarkerBeginEXT");
pfnCmdDebugMarkerEnd = (PFN_vkCmdDebugMarkerEndEXT)vkGetDeviceProcAddr(device, "vkCmdDebugMarkerEndEXT");
pfnCmdDebugMarkerInsert = (PFN_vkCmdDebugMarkerInsertEXT)vkGetDeviceProcAddr(device, "vkCmdDebugMarkerInsertEXT");
// Set flag if at least one function pointer is present
active = (pfnDebugMarkerSetObjectName != VK_NULL_HANDLE);
}
void setObjectName(VkDevice device, uint64_t object, VkDebugReportObjectTypeEXT objectType, const char *name)
{
// Check for valid function pointer (may not be present if not running in a debugging application)
if (pfnDebugMarkerSetObjectName)
{
VkDebugMarkerObjectNameInfoEXT nameInfo = {};
nameInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_NAME_INFO_EXT;
nameInfo.objectType = objectType;
nameInfo.object = object;
nameInfo.pObjectName = name;
pfnDebugMarkerSetObjectName(device, &nameInfo);
}
}
void setObjectTag(VkDevice device, uint64_t object, VkDebugReportObjectTypeEXT objectType, uint64_t name, size_t tagSize, const void* tag)
{
// Check for valid function pointer (may not be present if not running in a debugging application)
if (pfnDebugMarkerSetObjectTag)
{
VkDebugMarkerObjectTagInfoEXT tagInfo = {};
tagInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_TAG_INFO_EXT;
tagInfo.objectType = objectType;
tagInfo.object = object;
tagInfo.tagName = name;
tagInfo.tagSize = tagSize;
tagInfo.pTag = tag;
pfnDebugMarkerSetObjectTag(device, &tagInfo);
}
}
void beginRegion(VkCommandBuffer cmdbuffer, const char* pMarkerName, glm::vec4 color)
{
// Check for valid function pointer (may not be present if not running in a debugging application)
if (pfnCmdDebugMarkerBegin)
{
VkDebugMarkerMarkerInfoEXT markerInfo = {};
markerInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT;
memcpy(markerInfo.color, &color[0], sizeof(float) * 4);
markerInfo.pMarkerName = pMarkerName;
pfnCmdDebugMarkerBegin(cmdbuffer, &markerInfo);
}
}
void insert(VkCommandBuffer cmdbuffer, std::string markerName, glm::vec4 color)
{
// Check for valid function pointer (may not be present if not running in a debugging application)
if (pfnCmdDebugMarkerInsert)
{
VkDebugMarkerMarkerInfoEXT markerInfo = {};
markerInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT;
memcpy(markerInfo.color, &color[0], sizeof(float) * 4);
markerInfo.pMarkerName = markerName.c_str();
pfnCmdDebugMarkerInsert(cmdbuffer, &markerInfo);
}
}
void endRegion(VkCommandBuffer cmdBuffer)
{
// Check for valid function (may not be present if not runnin in a debugging application)
if (pfnCmdDebugMarkerEnd)
{
pfnCmdDebugMarkerEnd(cmdBuffer);
}
}
void setCommandBufferName(VkDevice device, VkCommandBuffer cmdBuffer, const char * name)
{
setObjectName(device, (uint64_t)cmdBuffer, VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT, name);
}
void setQueueName(VkDevice device, VkQueue queue, const char * name)
{
setObjectName(device, (uint64_t)queue, VK_DEBUG_REPORT_OBJECT_TYPE_QUEUE_EXT, name);
}
void setImageName(VkDevice device, VkImage image, const char * name)
{
setObjectName(device, (uint64_t)image, VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT, name);
}
void setSamplerName(VkDevice device, VkSampler sampler, const char * name)
{
setObjectName(device, (uint64_t)sampler, VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_EXT, name);
}
void setBufferName(VkDevice device, VkBuffer buffer, const char * name)
{
setObjectName(device, (uint64_t)buffer, VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT, name);
}
void setDeviceMemoryName(VkDevice device, VkDeviceMemory memory, const char * name)
{
setObjectName(device, (uint64_t)memory, VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_MEMORY_EXT, name);
}
void setShaderModuleName(VkDevice device, VkShaderModule shaderModule, const char * name)
{
setObjectName(device, (uint64_t)shaderModule, VK_DEBUG_REPORT_OBJECT_TYPE_SHADER_MODULE_EXT, name);
}
void setPipelineName(VkDevice device, VkPipeline pipeline, const char * name)
{
setObjectName(device, (uint64_t)pipeline, VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT, name);
}
void setPipelineLayoutName(VkDevice device, VkPipelineLayout pipelineLayout, const char * name)
{
setObjectName(device, (uint64_t)pipelineLayout, VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_LAYOUT_EXT, name);
}
void setRenderPassName(VkDevice device, VkRenderPass renderPass, const char * name)
{
setObjectName(device, (uint64_t)renderPass, VK_DEBUG_REPORT_OBJECT_TYPE_RENDER_PASS_EXT, name);
}
void setFramebufferName(VkDevice device, VkFramebuffer framebuffer, const char * name)
{
setObjectName(device, (uint64_t)framebuffer, VK_DEBUG_REPORT_OBJECT_TYPE_FRAMEBUFFER_EXT, name);
}
void setDescriptorSetLayoutName(VkDevice device, VkDescriptorSetLayout descriptorSetLayout, const char * name)
{
setObjectName(device, (uint64_t)descriptorSetLayout, VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT_EXT, name);
}
void setDescriptorSetName(VkDevice device, VkDescriptorSet descriptorSet, const char * name)
{
setObjectName(device, (uint64_t)descriptorSet, VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_EXT, name);
}
void setSemaphoreName(VkDevice device, VkSemaphore semaphore, const char * name)
{
setObjectName(device, (uint64_t)semaphore, VK_DEBUG_REPORT_OBJECT_TYPE_SEMAPHORE_EXT, name);
}
void setFenceName(VkDevice device, VkFence fence, const char * name)
{
setObjectName(device, (uint64_t)fence, VK_DEBUG_REPORT_OBJECT_TYPE_FENCE_EXT, name);
}
void setEventName(VkDevice device, VkEvent _event, const char * name)
{
setObjectName(device, (uint64_t)_event, VK_DEBUG_REPORT_OBJECT_TYPE_EVENT_EXT, name);
}
}
}
// vi: set sw=2 ts=4 expandtab:
@@ -0,0 +1,106 @@
/*
* Copyright 2017-2020 Mark Callow
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "vulkan/vulkan.h"
#include <math.h>
#include <stdlib.h>
#include <string>
#include <cstring>
#include <fstream>
#include <assert.h>
#include <stdio.h>
#include <vector>
#ifdef _WIN32
#include <windows.h>
#include <fcntl.h>
#include <io.h>
#endif
#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include "disable_glm_warnings.h"
#include <glm/glm.hpp>
#include "reenable_warnings.h"
namespace vkDebug
{
// Default validation layers
extern int validationLayerCount;
extern const char *validationLayerNames[];
// Default debug callback
VkBool32 messageCallback(
VkDebugReportFlagsEXT flags,
VkDebugReportObjectTypeEXT objType,
uint64_t srcObject,
size_t location,
int32_t msgCode,
const char* pLayerPrefix,
const char* pMsg,
void* pUserData);
// Load debug function pointers and set debug callback
// if callBack is NULL, default message callback will be used
void setupDebugging(
VkInstance instance,
VkDebugReportFlagsEXT flags,
VkDebugReportCallbackEXT callBack);
// Clear debug callback
void freeDebugCallback(VkInstance instance);
// Setup and functions for the VK_EXT_debug_marker_extension
// Extension spec can be found at https://github.com/KhronosGroup/Vulkan-Docs/blob/1.0-VK_EXT_debug_marker/doc/specs/vulkan/appendices/VK_EXT_debug_marker.txt
// Note that the extension will only be present if run from an offline debugging application
// The actual check for extension presence and enabling it on the device is done in the example base class
// See VulkanExampleBase::createInstance and VulkanExampleBase::createDevice (base/vulkanexamplebase.cpp)
namespace DebugMarker
{
// Set to true if function pointer for the debug marker are available
extern bool active;
// Get function pointers for the debug report extensions from the device
void setup(VkDevice device);
// Sets the debug name of an object
// All Objects in Vulkan are represented by their 64-bit handles which are passed into this function
// along with the object type
void setObjectName(VkDevice device, uint64_t object, VkDebugReportObjectTypeEXT objectType, const char *name);
// Set the tag for an object
void setObjectTag(VkDevice device, uint64_t object, VkDebugReportObjectTypeEXT objectType, uint64_t name, size_t tagSize, const void* tag);
// Start a new debug marker region
void beginRegion(VkCommandBuffer cmdbuffer, const char* pMarkerName, glm::vec4 color);
// Insert a new debug marker into the command buffer
void insert(VkCommandBuffer cmdbuffer, std::string markerName, glm::vec4 color);
// End the current debug marker region
void endRegion(VkCommandBuffer cmdBuffer);
// Object specific naming functions
void setCommandBufferName(VkDevice device, VkCommandBuffer cmdBuffer, const char * name);
void setQueueName(VkDevice device, VkQueue queue, const char * name);
void setImageName(VkDevice device, VkImage image, const char * name);
void setSamplerName(VkDevice device, VkSampler sampler, const char * name);
void setBufferName(VkDevice device, VkBuffer buffer, const char * name);
void setDeviceMemoryName(VkDevice device, VkDeviceMemory memory, const char * name);
void setShaderModuleName(VkDevice device, VkShaderModule shaderModule, const char * name);
void setPipelineName(VkDevice device, VkPipeline pipeline, const char * name);
void setPipelineLayoutName(VkDevice device, VkPipelineLayout pipelineLayout, const char * name);
void setRenderPassName(VkDevice device, VkRenderPass renderPass, const char * name);
void setFramebufferName(VkDevice device, VkFramebuffer framebuffer, const char * name);
void setDescriptorSetLayoutName(VkDevice device, VkDescriptorSetLayout descriptorSetLayout, const char * name);
void setDescriptorSetName(VkDevice device, VkDescriptorSet descriptorSet, const char * name);
void setSemaphoreName(VkDevice device, VkSemaphore semaphore, const char * name);
void setFenceName(VkDevice device, VkFence fence, const char * name);
void setEventName(VkDevice device, VkEvent _event, const char * name);
}
}
// vi: set sw=2 ts=4 expandtab:
@@ -0,0 +1,839 @@
/*
* Copyright 2016 Sascha Willems - www.saschawillems.de
* SPDX-License-Identifier: MIT
*
* Text overlay class for displaying debug information
*
*/
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <vector>
#include <sstream>
#include <iomanip>
#include <vulkan/vulkan.h>
#include "vulkantools.h"
#include "vulkandebug.h"
#include "stb/stb_font_consolas_24_latin1.inl"
// Defines for the STB font used
// STB font files can be found at http://nothings.org/stb/font/
#define STB_FONT_NAME stb_font_consolas_24_latin1
#define STB_FONT_WIDTH STB_FONT_consolas_24_latin1_BITMAP_WIDTH
#define STB_FONT_HEIGHT STB_FONT_consolas_24_latin1_BITMAP_HEIGHT
#define STB_FIRST_CHAR STB_FONT_consolas_24_latin1_FIRST_CHAR
#define STB_NUM_CHARS STB_FONT_consolas_24_latin1_NUM_CHARS
#define STB_MISSING_GLPYH 0x80 // Actually a control character.
// Max. number of chars the text overlay buffer can hold
#define MAX_CHAR_COUNT 1024
/**
* @internal
* @brief Given the lead byte of a UTF-8 sequence returns the expected length of the codepoint
* @param[in] leadByte The lead byte of a UTF-8 sequence
* @return The expected length of the codepoint */
[[nodiscard]] constexpr inline int sequenceLength(uint8_t leadByte) noexcept {
if ((leadByte & 0b1000'0000u) == 0b0000'0000u)
return 1;
if ((leadByte & 0b1110'0000u) == 0b1100'0000u)
return 2;
if ((leadByte & 0b1111'0000u) == 0b1110'0000u)
return 3;
if ((leadByte & 0b1111'1000u) == 0b1111'0000u)
return 4;
return 0;
}
/**
* @internal
* @brief Checks if the codepoint was coded as a longer than required sequence
* @param[in] codepoint The unicode codepoint
* @param[in] length The UTF-8 sequence length
* @return True if the sequence length was inappropriate for the given codepoint */
[[nodiscard]] constexpr inline bool isOverlongSequence(uint32_t codepoint, int length) noexcept {
if (codepoint < 0x80)
return length != 1;
else if (codepoint < 0x800)
return length != 2;
else if (codepoint < 0x10000)
return length != 3;
else
return false;
}
/**
* @internal
* @brief Checks if the codepoint is valid
* @param[in] codepoint The unicode codepoint
* @return True if the codepoint is a valid unicode codepoint */
[[nodiscard]] constexpr inline bool isCodepointValid(uint32_t codepoint) noexcept {
return codepoint <= 0x0010FFFFu
&& !(0xD800u <= codepoint && codepoint <= 0xDBFFu);
}
/**
* @internal
* @brief Safely checks and advances a UTF-8 sequence iterator to the start of the next unicode codepoint
* @param[in] it iterator to be advanced
* @param[in] end iterator pointing to the end of the range
* @return True if the advance operation was successful and the advanced codepoint was a valid UTF-8 sequence */
template <typename Iterator>
[[nodiscard]] constexpr bool advanceUTF8(Iterator& it, Iterator end,
uint32_t& codepoint) noexcept {
if (it == end)
return false;
const auto length = sequenceLength(*it);
if (length == 0)
return false;
if (std::distance(it, end) < length)
return false;
for (int i = 1; i < length; ++i) {
const auto trailByte = *(it + i);
if ((static_cast<uint8_t>(trailByte) & 0b1100'0000u) != 0b1000'0000u)
return false;
}
codepoint = 0;
switch (length) {
case 1:
codepoint |= *it++;
break;
case 2:
codepoint |= (*it++ & 0b0001'1111u) << 6u;
codepoint |= (*it++ & 0b0011'1111u);
break;
case 3:
codepoint |= (*it++ & 0b0000'1111u) << 12u;
codepoint |= (*it++ & 0b0011'1111u) << 6u;
codepoint |= (*it++ & 0b0011'1111u);
break;
case 4:
codepoint |= (*it++ & 0b0000'0111u) << 18u;
codepoint |= (*it++ & 0b0011'1111u) << 12u;
codepoint |= (*it++ & 0b0011'1111u) << 6u;
codepoint |= (*it++ & 0b0011'1111u);
break;
}
if (!isCodepointValid(codepoint))
return false;
if (isOverlongSequence(codepoint, length))
return false;
return true;
}
// Mostly self-contained text overlay class
// todo : comment
class VulkanTextOverlay
{
private:
VkPhysicalDevice physicalDevice;
VkDevice device;
VkPhysicalDeviceMemoryProperties deviceMemoryProperties;
VkQueue queue;
VkFormat colorFormat;
VkFormat depthFormat;
uint32_t *frameBufferWidth;
uint32_t *frameBufferHeight;
VkSampler sampler;
VkImage image;
VkImageView view;
VkBuffer buffer;
VkDeviceMemory memory;
VkDeviceMemory imageMemory;
VkDescriptorPool descriptorPool;
VkDescriptorSetLayout descriptorSetLayout;
VkDescriptorSet descriptorSet;
VkPipelineLayout pipelineLayout;
VkPipelineCache pipelineCache;
VkPipeline pipeline;
VkRenderPass renderPass;
VkCommandPool commandPool;
std::vector<VkFramebuffer*> frameBuffers;
std::vector<VkPipelineShaderStageCreateInfo> shaderStages;
// Pointer to mapped vertex buffer
glm::vec4 *mapped = nullptr;
// Used during text updates
glm::vec4 *mappedLocal = nullptr;
stb_fontchar stbFontData[STB_NUM_CHARS];
uint32_t numLetters;
// Try to find appropriate memory type for a memory allocation
uint32_t getMemoryType(uint32_t typeBits, VkFlags properties)
{
for (uint32_t i = 0; i < 32; i++)
{
if ((typeBits & 1) == 1)
{
if ((deviceMemoryProperties.memoryTypes[i].propertyFlags & properties) == properties)
{
return i;
}
}
typeBits >>= 1;
}
// todo : throw error
return 0;
}
public:
enum TextAlign { alignLeft, alignCenter, alignRight };
bool visible = true;
bool invalidated = false;
std::vector<VkCommandBuffer> cmdBuffers;
VulkanTextOverlay(
VkPhysicalDevice physicalDevice,
VkDevice device,
VkQueue queue,
std::vector<VkFramebuffer> &framebuffers,
VkFormat colorformat,
VkFormat depthformat,
uint32_t *framebufferwidth,
uint32_t *framebufferheight,
std::vector<VkPipelineShaderStageCreateInfo> shaderstages)
{
this->physicalDevice = physicalDevice;
this->device = device;
this->queue = queue;
this->colorFormat = colorformat;
this->depthFormat = depthformat;
this->frameBuffers.resize(framebuffers.size());
for (uint32_t i = 0; i < framebuffers.size(); i++)
{
this->frameBuffers[i] = &framebuffers[i];
}
this->shaderStages = shaderstages;
this->frameBufferWidth = framebufferwidth;
this->frameBufferHeight = framebufferheight;
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &deviceMemoryProperties);
cmdBuffers.resize(framebuffers.size());
prepareResources();
prepareRenderPass();
preparePipeline();
}
~VulkanTextOverlay()
{
// Free up all Vulkan resources requested by the text overlay
vkDestroySampler(device, sampler, nullptr);
vkDestroyImage(device, image, nullptr);
vkDestroyImageView(device, view, nullptr);
vkDestroyBuffer(device, buffer, nullptr);
vkFreeMemory(device, memory, nullptr);
vkFreeMemory(device, imageMemory, nullptr);
vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
vkDestroyDescriptorPool(device, descriptorPool, nullptr);
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
vkDestroyPipelineCache(device, pipelineCache, nullptr);
vkDestroyPipeline(device, pipeline, nullptr);
vkDestroyRenderPass(device, renderPass, nullptr);
vkFreeCommandBuffers(device, commandPool, static_cast<uint32_t>(cmdBuffers.size()), cmdBuffers.data());
vkDestroyCommandPool(device, commandPool, nullptr);
}
// Prepare all vulkan resources required to render the font
// The text overlay uses separate resources for descriptors (pool, sets, layouts), pipelines and command buffers
void prepareResources()
{
static unsigned char font24pixels[STB_FONT_HEIGHT][STB_FONT_WIDTH];
STB_FONT_NAME(stbFontData, font24pixels, STB_FONT_HEIGHT);
// Command buffer
// Pool
VkCommandPoolCreateInfo cmdPoolInfo = {};
cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
cmdPoolInfo.queueFamilyIndex = 0; // todo : pass from example base / swap chain
cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
VK_CHECK_RESULT(vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &commandPool));
VkCommandBufferAllocateInfo cmdBufAllocateInfo =
vkTools::initializers::commandBufferAllocateInfo(
commandPool,
VK_COMMAND_BUFFER_LEVEL_PRIMARY,
(uint32_t)cmdBuffers.size());
VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, cmdBuffers.data()));
// Vertex buffer, 4 per character.
VkDeviceSize bufferSize = MAX_CHAR_COUNT * sizeof(glm::vec4) * 4;
VkBufferCreateInfo bufferInfo = vkTools::initializers::bufferCreateInfo(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, bufferSize);
VK_CHECK_RESULT(vkCreateBuffer(device, &bufferInfo, nullptr, &buffer));
VkMemoryRequirements memReqs;
VkMemoryAllocateInfo allocInfo = vkTools::initializers::memoryAllocateInfo();
vkGetBufferMemoryRequirements(device, buffer, &memReqs);
allocInfo.allocationSize = memReqs.size;
allocInfo.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &memory));
VK_CHECK_RESULT(vkBindBufferMemory(device, buffer, memory, 0));
// Map persistent
VK_CHECK_RESULT(vkMapMemory(device, memory, 0, VK_WHOLE_SIZE, 0, (void **)&mapped));
// Font texture
VkImageCreateInfo imageInfo = vkTools::initializers::imageCreateInfo();
imageInfo.imageType = VK_IMAGE_TYPE_2D;
imageInfo.format = VK_FORMAT_R8_UNORM;
imageInfo.extent.width = STB_FONT_WIDTH;
imageInfo.extent.height = STB_FONT_HEIGHT;
imageInfo.extent.depth = 1;
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = 1;
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
VK_CHECK_RESULT(vkCreateImage(device, &imageInfo, nullptr, &image));
vkGetImageMemoryRequirements(device, image, &memReqs);
allocInfo.allocationSize = memReqs.size;
allocInfo.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory));
VK_CHECK_RESULT(vkBindImageMemory(device, image, imageMemory, 0));
// Staging
struct {
VkDeviceMemory memory;
VkBuffer buffer;
} stagingBuffer;
VkBufferCreateInfo bufferCreateInfo = vkTools::initializers::bufferCreateInfo();
bufferCreateInfo.size = allocInfo.allocationSize;
bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VK_CHECK_RESULT(vkCreateBuffer(device, &bufferCreateInfo, nullptr, &stagingBuffer.buffer));
// Get memory requirements for the staging buffer (alignment, memory type bits)
vkGetBufferMemoryRequirements(device, stagingBuffer.buffer, &memReqs);
allocInfo.allocationSize = memReqs.size;
// Get memory type index for a host visible buffer
allocInfo.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &stagingBuffer.memory));
VK_CHECK_RESULT(vkBindBufferMemory(device, stagingBuffer.buffer, stagingBuffer.memory, 0));
uint8_t *data;
VK_CHECK_RESULT(vkMapMemory(device, stagingBuffer.memory, 0, allocInfo.allocationSize, 0, (void **)&data));
memcpy(data, &font24pixels[0][0], STB_FONT_WIDTH * STB_FONT_HEIGHT);
vkUnmapMemory(device, stagingBuffer.memory);
// Copy to image
VkCommandBuffer copyCmd;
cmdBufAllocateInfo.commandBufferCount = 1;
VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &copyCmd));
VkCommandBufferBeginInfo cmdBufInfo = vkTools::initializers::commandBufferBeginInfo();
VK_CHECK_RESULT(vkBeginCommandBuffer(copyCmd, &cmdBufInfo));
// Prepare for transfer
vkTools::setImageLayout(
copyCmd,
image,
VK_IMAGE_ASPECT_COLOR_BIT,
VK_IMAGE_LAYOUT_PREINITIALIZED,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
VkBufferImageCopy bufferCopyRegion = {};
bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
bufferCopyRegion.imageSubresource.mipLevel = 0;
bufferCopyRegion.imageSubresource.layerCount = 1;
bufferCopyRegion.imageExtent.width = STB_FONT_WIDTH;
bufferCopyRegion.imageExtent.height = STB_FONT_HEIGHT;
bufferCopyRegion.imageExtent.depth = 1;
vkCmdCopyBufferToImage(
copyCmd,
stagingBuffer.buffer,
image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1,
&bufferCopyRegion
);
// Prepare for shader read
vkTools::setImageLayout(
copyCmd,
image,
VK_IMAGE_ASPECT_COLOR_BIT,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
VK_CHECK_RESULT(vkEndCommandBuffer(copyCmd));
VkSubmitInfo submitInfo = vkTools::initializers::submitInfo();
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &copyCmd;
VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
VK_CHECK_RESULT(vkQueueWaitIdle(queue));
vkFreeCommandBuffers(device, commandPool, 1, &copyCmd);
vkFreeMemory(device, stagingBuffer.memory, nullptr);
vkDestroyBuffer(device, stagingBuffer.buffer, nullptr);
VkImageViewCreateInfo imageViewInfo = vkTools::initializers::imageViewCreateInfo();
imageViewInfo.image = image;
imageViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
imageViewInfo.format = imageInfo.format;
imageViewInfo.components = {
VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY
};
imageViewInfo.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
VK_CHECK_RESULT(vkCreateImageView(device, &imageViewInfo, nullptr, &view));
// Sampler
VkSamplerCreateInfo samplerInfo = vkTools::initializers::samplerCreateInfo();
samplerInfo.magFilter = VK_FILTER_LINEAR;
samplerInfo.minFilter = VK_FILTER_LINEAR;
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
samplerInfo.mipLodBias = 0.0f;
samplerInfo.compareOp = VK_COMPARE_OP_NEVER;
samplerInfo.minLod = 0.0f;
samplerInfo.maxLod = 1.0f;
samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
samplerInfo.anisotropyEnable = VK_FALSE;
samplerInfo.maxAnisotropy = 1.0;
VK_CHECK_RESULT(vkCreateSampler(device, &samplerInfo, nullptr, &sampler));
// Descriptor
// Font uses a separate descriptor pool
std::array<VkDescriptorPoolSize, 1> poolSizes;
poolSizes[0] = vkTools::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1);
VkDescriptorPoolCreateInfo descriptorPoolInfo =
vkTools::initializers::descriptorPoolCreateInfo(
static_cast<uint32_t>(poolSizes.size()),
poolSizes.data(),
1);
VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
// Descriptor set layout
std::array<VkDescriptorSetLayoutBinding, 1> setLayoutBindings;
setLayoutBindings[0] = vkTools::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0);
VkDescriptorSetLayoutCreateInfo descriptorSetLayoutInfo =
vkTools::initializers::descriptorSetLayoutCreateInfo(
setLayoutBindings.data(),
static_cast<uint32_t>(setLayoutBindings.size()));
VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutInfo, nullptr, &descriptorSetLayout));
// Pipeline layout
VkPipelineLayoutCreateInfo pipelineLayoutInfo =
vkTools::initializers::pipelineLayoutCreateInfo(
&descriptorSetLayout,
1);
VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout));
// Descriptor set
VkDescriptorSetAllocateInfo descriptorSetAllocInfo =
vkTools::initializers::descriptorSetAllocateInfo(
descriptorPool,
&descriptorSetLayout,
1);
VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorSetAllocInfo, &descriptorSet));
VkDescriptorImageInfo texDescriptor =
vkTools::initializers::descriptorImageInfo(
sampler,
view,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
std::array<VkWriteDescriptorSet, 1> writeDescriptorSets;
writeDescriptorSets[0] = vkTools::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &texDescriptor);
vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
// Pipeline cache
VkPipelineCacheCreateInfo pipelineCacheCreateInfo = {};
pipelineCacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
VK_CHECK_RESULT(vkCreatePipelineCache(device, &pipelineCacheCreateInfo, nullptr, &pipelineCache));
}
// Prepare a separate pipeline for the font rendering decoupled from the main application
void preparePipeline()
{
VkPipelineInputAssemblyStateCreateInfo inputAssemblyState =
vkTools::initializers::pipelineInputAssemblyStateCreateInfo(
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP,
0,
// primmitiveRestartEnable not needed but disabling it results in a MoltenVK
// feature not present warning.
VK_TRUE);
VkPipelineRasterizationStateCreateInfo rasterizationState =
vkTools::initializers::pipelineRasterizationStateCreateInfo(
VK_POLYGON_MODE_FILL,
VK_CULL_MODE_BACK_BIT,
VK_FRONT_FACE_CLOCKWISE,
0);
// Because we haven't enabled the depthClamp device feature.
rasterizationState.depthClampEnable = VK_FALSE;
// Enable blending
VkPipelineColorBlendAttachmentState blendAttachmentState =
vkTools::initializers::pipelineColorBlendAttachmentState(0xf, VK_TRUE);
blendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
blendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
blendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD;
blendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
blendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
blendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD;
blendAttachmentState.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
VkPipelineColorBlendStateCreateInfo colorBlendState =
vkTools::initializers::pipelineColorBlendStateCreateInfo(
1,
&blendAttachmentState);
VkPipelineDepthStencilStateCreateInfo depthStencilState =
vkTools::initializers::pipelineDepthStencilStateCreateInfo(
VK_FALSE,
VK_FALSE,
VK_COMPARE_OP_LESS_OR_EQUAL);
VkPipelineViewportStateCreateInfo viewportState =
vkTools::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
VkPipelineMultisampleStateCreateInfo multisampleState =
vkTools::initializers::pipelineMultisampleStateCreateInfo(
VK_SAMPLE_COUNT_1_BIT,
0);
std::vector<VkDynamicState> dynamicStateEnables = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR
};
VkPipelineDynamicStateCreateInfo dynamicState =
vkTools::initializers::pipelineDynamicStateCreateInfo(
dynamicStateEnables.data(),
static_cast<uint32_t>(dynamicStateEnables.size()),
0);
std::array<VkVertexInputBindingDescription, 2> vertexBindings = {};
vertexBindings[0] = vkTools::initializers::vertexInputBindingDescription(0, sizeof(glm::vec4), VK_VERTEX_INPUT_RATE_VERTEX);
vertexBindings[1] = vkTools::initializers::vertexInputBindingDescription(1, sizeof(glm::vec4), VK_VERTEX_INPUT_RATE_VERTEX);
std::array<VkVertexInputAttributeDescription, 2> vertexAttribs = {};
// Position
vertexAttribs[0] = vkTools::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32_SFLOAT, 0);
// UV
vertexAttribs[1] = vkTools::initializers::vertexInputAttributeDescription(1, 1, VK_FORMAT_R32G32_SFLOAT, sizeof(glm::vec2));
VkPipelineVertexInputStateCreateInfo inputState = vkTools::initializers::pipelineVertexInputStateCreateInfo();
inputState.vertexBindingDescriptionCount = static_cast<uint32_t>(vertexBindings.size());
inputState.pVertexBindingDescriptions = vertexBindings.data();
inputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexAttribs.size());
inputState.pVertexAttributeDescriptions = vertexAttribs.data();
VkGraphicsPipelineCreateInfo pipelineCreateInfo =
vkTools::initializers::pipelineCreateInfo(
pipelineLayout,
renderPass,
0);
pipelineCreateInfo.pVertexInputState = &inputState;
pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
pipelineCreateInfo.pRasterizationState = &rasterizationState;
pipelineCreateInfo.pColorBlendState = &colorBlendState;
pipelineCreateInfo.pMultisampleState = &multisampleState;
pipelineCreateInfo.pViewportState = &viewportState;
pipelineCreateInfo.pDepthStencilState = &depthStencilState;
pipelineCreateInfo.pDynamicState = &dynamicState;
pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
pipelineCreateInfo.pStages = shaderStages.data();
VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipeline));
}
// Prepare a separate render pass for rendering the text as an overlay
void prepareRenderPass()
{
VkAttachmentDescription attachments[2] = {};
// Color attachment
attachments[0].format = colorFormat;
attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
// Don't clear the framebuffer (like the renderpass from the example does)
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachments[0].initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachments[0].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
// Depth attachment
attachments[1].format = depthFormat;
attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachments[1].initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
VkAttachmentReference colorReference = {};
colorReference.attachment = 0;
colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkAttachmentReference depthReference = {};
depthReference.attachment = 1;
depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
VkSubpassDescription subpass = {};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.flags = 0;
subpass.inputAttachmentCount = 0;
subpass.pInputAttachments = NULL;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorReference;
subpass.pResolveAttachments = NULL;
subpass.pDepthStencilAttachment = &depthReference;
subpass.preserveAttachmentCount = 0;
subpass.pPreserveAttachments = NULL;
VkRenderPassCreateInfo renderPassInfo = {};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassInfo.pNext = NULL;
renderPassInfo.attachmentCount = 2;
renderPassInfo.pAttachments = attachments;
renderPassInfo.subpassCount = 1;
renderPassInfo.pSubpasses = &subpass;
renderPassInfo.dependencyCount = 0;
renderPassInfo.pDependencies = NULL;
VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass));
}
// Map buffer
void beginTextUpdate()
{
mappedLocal = mapped;
numLetters = 0;
}
// Add text to the current buffer
// todo : drop shadow? color attribute?
void addText(std::string text, float x, float y, TextAlign align)
{
if (numLetters == MAX_CHAR_COUNT)
return;
assert(mapped != nullptr);
const float charW = 1.5f / *frameBufferWidth;
const float charH = 1.5f / *frameBufferHeight;
float fbW = (float)*frameBufferWidth;
float fbH = (float)*frameBufferHeight;
x = (x / fbW * 2.0f) - 1.0f;
y = (y / fbH * 2.0f) - 1.0f;
// Calculate text width
float textWidth = 0;
uint32_t codepoint;
auto it = text.begin();
while (it != text.end())
{
if (!advanceUTF8(it, text.end(), codepoint))
break;
// TODO: Get a UTF8 font. Consider changing to Dear ImGUI
// https://github.com/ocornut/imgui.
// Placeholder to avoid crashing.
if (codepoint > STB_NUM_CHARS + STB_FIRST_CHAR)
codepoint = STB_MISSING_GLPYH;
stb_fontchar *charData = &stbFontData[(uint32_t)codepoint - STB_FIRST_CHAR];
textWidth += charData->advance * charW;
}
switch (align)
{
case alignRight:
x -= textWidth;
break;
case alignCenter:
x -= textWidth / 2.0f;
break;
case alignLeft:
break;
}
// Generate a uv mapped quad per char in the new text
it = text.begin();
while (it != text.end())
{
if (!advanceUTF8(it, text.end(), codepoint))
break;
if (codepoint > STB_NUM_CHARS + STB_FIRST_CHAR)
codepoint = STB_MISSING_GLPYH;
stb_fontchar *charData = &stbFontData[(uint32_t)codepoint - STB_FIRST_CHAR];
mappedLocal->x = (x + (float)charData->x0 * charW);
mappedLocal->y = (y + (float)charData->y0 * charH);
mappedLocal->z = charData->s0;
mappedLocal->w = charData->t0;
mappedLocal++;
mappedLocal->x = (x + (float)charData->x1 * charW);
mappedLocal->y = (y + (float)charData->y0 * charH);
mappedLocal->z = charData->s1;
mappedLocal->w = charData->t0;
mappedLocal++;
mappedLocal->x = (x + (float)charData->x0 * charW);
mappedLocal->y = (y + (float)charData->y1 * charH);
mappedLocal->z = charData->s0;
mappedLocal->w = charData->t1;
mappedLocal++;
mappedLocal->x = (x + (float)charData->x1 * charW);
mappedLocal->y = (y + (float)charData->y1 * charH);
mappedLocal->z = charData->s1;
mappedLocal->w = charData->t1;
mappedLocal++;
x += charData->advance * charW;
numLetters++;
if (numLetters == MAX_CHAR_COUNT)
break; // Truncate the text.
}
}
// Unmap buffer and update command buffers
void endTextUpdate()
{
updateCommandBuffers();
}
// Needs to be called by the application
void updateCommandBuffers()
{
VkCommandBufferBeginInfo cmdBufInfo = vkTools::initializers::commandBufferBeginInfo();
VkRenderPassBeginInfo renderPassBeginInfo = vkTools::initializers::renderPassBeginInfo();
renderPassBeginInfo.renderPass = renderPass;
renderPassBeginInfo.renderArea.extent.width = *frameBufferWidth;
renderPassBeginInfo.renderArea.extent.height = *frameBufferHeight;
renderPassBeginInfo.clearValueCount = 0;
renderPassBeginInfo.pClearValues = nullptr;
for (uint32_t i = 0; i < cmdBuffers.size(); ++i)
{
renderPassBeginInfo.framebuffer = *frameBuffers[i];
VK_CHECK_RESULT(vkBeginCommandBuffer(cmdBuffers[i], &cmdBufInfo));
if (vkDebug::DebugMarker::active)
{
vkDebug::DebugMarker::beginRegion(cmdBuffers[i], "Text overlay", glm::vec4(1.0f, 0.94f, 0.3f, 1.0f));
}
vkCmdBeginRenderPass(cmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
VkViewport viewport = vkTools::initializers::viewport((float)*frameBufferWidth, (float)*frameBufferHeight, 0.0f, 1.0f);
vkCmdSetViewport(cmdBuffers[i], 0, 1, &viewport);
VkRect2D scissor = vkTools::initializers::rect2D(*frameBufferWidth, *frameBufferHeight, 0, 0);
vkCmdSetScissor(cmdBuffers[i], 0, 1, &scissor);
vkCmdBindPipeline(cmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
vkCmdBindDescriptorSets(cmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
VkDeviceSize offsets = 0;
vkCmdBindVertexBuffers(cmdBuffers[i], 0, 1, &buffer, &offsets);
vkCmdBindVertexBuffers(cmdBuffers[i], 1, 1, &buffer, &offsets);
for (uint32_t j = 0; j < numLetters; j++)
{
vkCmdDraw(cmdBuffers[i], 4, 1, j * 4, 0);
}
vkCmdEndRenderPass(cmdBuffers[i]);
if (vkDebug::DebugMarker::active)
{
vkDebug::DebugMarker::endRegion(cmdBuffers[i]);
}
VK_CHECK_RESULT(vkEndCommandBuffer(cmdBuffers[i]));
}
}
// Submit the text command buffers to a queue
void submit(VkQueue targetQueue, uint32_t bufferindex, VkSubmitInfo submitInfo)
{
if (!visible)
{
return;
}
submitInfo.pCommandBuffers = &cmdBuffers[bufferindex];
submitInfo.commandBufferCount = 1;
VK_CHECK_RESULT(vkQueueSubmit(targetQueue, 1, &submitInfo, VK_NULL_HANDLE));
}
void reallocateCommandBuffers()
{
vkFreeCommandBuffers(device, commandPool, static_cast<uint32_t>(cmdBuffers.size()), cmdBuffers.data());
VkCommandBufferAllocateInfo cmdBufAllocateInfo =
vkTools::initializers::commandBufferAllocateInfo(
commandPool,
VK_COMMAND_BUFFER_LEVEL_PRIMARY,
static_cast<uint32_t>(cmdBuffers.size()));
VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, cmdBuffers.data()));
}
};
// vi: set sw=2 ts=4 expandtab:
@@ -0,0 +1,918 @@
/*
* Copyright 2016 Sascha Willems - www.saschawillems.de
* SPDX-License-Identifier: MIT
*
* Assorted commonly used Vulkan helper functions
*
* asserts on unhandled cases in setImageLayout added by Mark Callow, 2017.3.3.
*/
#if defined(_WIN32)
#define _CRT_SECURE_NO_WARNINGS // For fopen
#endif
#include "vulkantools.h"
#include "unused.h"
namespace vkTools
{
VkBool32 checkGlobalExtensionPresent(const char* extensionName)
{
uint32_t extensionCount = 0;
std::vector<VkExtensionProperties> extensions;
vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, NULL);
extensions.resize(extensionCount);
vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, extensions.data());
for (auto& ext : extensions)
{
if (!strcmp(extensionName, ext.extensionName))
{
return true;
}
}
return false;
}
VkBool32 checkDeviceExtensionPresent(VkPhysicalDevice physicalDevice, const char* extensionName)
{
uint32_t extensionCount = 0;
std::vector<VkExtensionProperties> extensions;
vkEnumerateDeviceExtensionProperties(physicalDevice, NULL, &extensionCount, NULL);
extensions.resize(extensionCount);
vkEnumerateDeviceExtensionProperties(physicalDevice, NULL, &extensionCount, extensions.data());
for (auto& ext : extensions)
{
if (!strcmp(extensionName, ext.extensionName))
{
return true;
}
}
return false;
}
std::string errorString(VkResult errorCode)
{
switch (errorCode)
{
#define STR(r) case VK_ ##r: return #r
STR(NOT_READY);
STR(TIMEOUT);
STR(EVENT_SET);
STR(EVENT_RESET);
STR(INCOMPLETE);
STR(ERROR_OUT_OF_HOST_MEMORY);
STR(ERROR_OUT_OF_DEVICE_MEMORY);
STR(ERROR_INITIALIZATION_FAILED);
STR(ERROR_DEVICE_LOST);
STR(ERROR_MEMORY_MAP_FAILED);
STR(ERROR_LAYER_NOT_PRESENT);
STR(ERROR_EXTENSION_NOT_PRESENT);
STR(ERROR_FEATURE_NOT_PRESENT);
STR(ERROR_INCOMPATIBLE_DRIVER);
STR(ERROR_TOO_MANY_OBJECTS);
STR(ERROR_FORMAT_NOT_SUPPORTED);
STR(ERROR_SURFACE_LOST_KHR);
STR(ERROR_NATIVE_WINDOW_IN_USE_KHR);
STR(SUBOPTIMAL_KHR);
STR(ERROR_OUT_OF_DATE_KHR);
STR(ERROR_INCOMPATIBLE_DISPLAY_KHR);
STR(ERROR_VALIDATION_FAILED_EXT);
STR(ERROR_INVALID_SHADER_NV);
#undef STR
default:
return "UNKNOWN_ERROR";
}
}
VkBool32 getSupportedDepthFormat(VkPhysicalDevice physicalDevice, VkFormat *depthFormat)
{
// Since all depth formats may be optional, we need to find a suitable depth format to use
// Start with the highest precision packed format
std::vector<VkFormat> depthFormats = {
VK_FORMAT_D32_SFLOAT_S8_UINT,
VK_FORMAT_D32_SFLOAT,
VK_FORMAT_D24_UNORM_S8_UINT,
VK_FORMAT_D16_UNORM_S8_UINT,
VK_FORMAT_D16_UNORM
};
for (auto& format : depthFormats)
{
VkFormatProperties formatProps;
vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &formatProps);
// Format must support depth stencil attachment for optimal tiling
if (formatProps.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)
{
*depthFormat = format;
return true;
}
}
return false;
}
// Create an image memory barrier for changing the layout of
// an image and put it into an active command buffer
// See chapter 11.4 "Image Layout" for details
void setImageLayout(
VkCommandBuffer cmdbuffer,
VkImage image,
VkImageAspectFlags /*aspectMask*/,
VkImageLayout oldImageLayout,
VkImageLayout newImageLayout,
VkImageSubresourceRange subresourceRange)
{
// Create an image barrier object
VkImageMemoryBarrier imageMemoryBarrier = vkTools::initializers::imageMemoryBarrier();
imageMemoryBarrier.oldLayout = oldImageLayout;
imageMemoryBarrier.newLayout = newImageLayout;
imageMemoryBarrier.image = image;
imageMemoryBarrier.subresourceRange = subresourceRange;
// Source layouts (old)
// Source access mask controls actions that have to be finished on the old layout
// before it will be transitioned to the new layout
switch (oldImageLayout)
{
case VK_IMAGE_LAYOUT_UNDEFINED:
// Image layout is undefined (or does not matter)
// Only valid as initial layout
// No flags required, listed only for completeness
imageMemoryBarrier.srcAccessMask = 0;
break;
case VK_IMAGE_LAYOUT_PREINITIALIZED:
// Image is preinitialized
// Only valid as initial layout for linear images, preserves memory contents
// Make sure host writes have been finished
imageMemoryBarrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
// Image is a color attachment
// Make sure any writes to the color buffer have been finished
imageMemoryBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
// Image is a depth/stencil attachment
// Make sure any writes to the depth/stencil buffer have been finished
imageMemoryBarrier.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
// Image is a transfer source
// Make sure any reads from the image have been finished
imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
break;
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
// Image is a transfer destination
// Make sure any writes to the image have been finished
imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
// Image is read by a shader
// Make sure any shader reads from the image have been finished
imageMemoryBarrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
break;
default:
assert(0); // Attempt to use unhandled case.
}
// Target layouts (new)
// Destination access mask controls the dependency for the new image layout
switch (newImageLayout)
{
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
// Image will be used as a transfer destination
// Make sure any writes to the image have been finished
imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
// Image will be used as a transfer source
// Make sure any reads from and writes to the image have been finished
imageMemoryBarrier.srcAccessMask = imageMemoryBarrier.srcAccessMask | VK_ACCESS_TRANSFER_READ_BIT;
imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
break;
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
// Image will be used as a color attachment
// Make sure any writes to the color buffer have been finished
imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
imageMemoryBarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
// Image layout will be used as a depth/stencil attachment
// Make sure any writes to depth/stencil buffer have been finished
imageMemoryBarrier.dstAccessMask = imageMemoryBarrier.dstAccessMask | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
// Image will be read in a shader (sampler, input attachment)
// Make sure any writes to the image have been finished
if (imageMemoryBarrier.srcAccessMask == 0)
{
imageMemoryBarrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
}
imageMemoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
break;
default:
assert(0); // Attempt to use unhandled case.
}
// Put barrier on top
VkPipelineStageFlags srcStageFlags = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
VkPipelineStageFlags destStageFlags = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
// Put barrier inside setup command buffer
vkCmdPipelineBarrier(
cmdbuffer,
srcStageFlags,
destStageFlags,
0,
0, nullptr,
0, nullptr,
1, &imageMemoryBarrier);
}
// Fixed sub resource on first mip level and layer
void setImageLayout(
VkCommandBuffer cmdbuffer,
VkImage image,
VkImageAspectFlags aspectMask,
VkImageLayout oldImageLayout,
VkImageLayout newImageLayout)
{
VkImageSubresourceRange subresourceRange = {};
subresourceRange.aspectMask = aspectMask;
subresourceRange.baseMipLevel = 0;
subresourceRange.levelCount = 1;
subresourceRange.layerCount = 1;
setImageLayout(cmdbuffer, image, aspectMask, oldImageLayout, newImageLayout, subresourceRange);
}
void exitFatal(std::string message, std::string caption)
{
(void)SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, caption.c_str(),
message.c_str(), NULL);
std::cerr << message << "\n";
exit(1);
}
std::string readTextFile(const char *fileName)
{
std::string fileContent;
std::ifstream fileStream(fileName, std::ios::in);
if (!fileStream.is_open()) {
printf("File %s not found\n", fileName);
return "";
}
std::string line = "";
while (!fileStream.eof()) {
getline(fileStream, line);
fileContent.append(line + "\n");
}
fileStream.close();
return fileContent;
}
#if defined(__ANDROID__)
// Android shaders are stored as assets in the apk
// So they need to be loaded via the asset manager
VkShaderModule loadShader(AAssetManager* assetManager, const char *fileName, VkDevice device, VkShaderStageFlagBits stage)
{
// Load shader from compressed asset
AAsset* asset = AAssetManager_open(assetManager, fileName, AASSET_MODE_STREAMING);
assert(asset);
size_t size = AAsset_getLength(asset);
assert(size > 0);
char *shaderCode = new char[size];
AAsset_read(asset, shaderCode, size);
AAsset_close(asset);
VkShaderModule shaderModule;
VkShaderModuleCreateInfo moduleCreateInfo;
moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
moduleCreateInfo.pNext = NULL;
moduleCreateInfo.codeSize = size;
moduleCreateInfo.pCode = (uint32_t*)shaderCode;
moduleCreateInfo.flags = 0;
VK_CHECK_RESULT(vkCreateShaderModule(device, &moduleCreateInfo, NULL, &shaderModule));
delete[] shaderCode;
return shaderModule;
}
#else
VkShaderModule loadShader(const char *fileName, VkDevice device, VkShaderStageFlagBits /*stage*/)
{
size_t size;
FILE *fp = fopen(fileName, "rb");
assert(fp);
fseek(fp, 0L, SEEK_END);
size = ftell(fp);
fseek(fp, 0L, SEEK_SET);
//shaderCode = malloc(size);
char *shaderCode = new char[size];
U_ASSERT_ONLY size_t retval = fread(shaderCode, size, 1, fp);
assert(retval == 1);
(void)retval; // Supress VC++ unused variable warning for Release config.
assert(size > 0);
fclose(fp);
VkShaderModule shaderModule;
VkShaderModuleCreateInfo moduleCreateInfo;
moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
moduleCreateInfo.pNext = NULL;
moduleCreateInfo.codeSize = size;
moduleCreateInfo.pCode = (uint32_t*)shaderCode;
moduleCreateInfo.flags = 0;
VK_CHECK_RESULT(vkCreateShaderModule(device, &moduleCreateInfo, NULL, &shaderModule));
delete[] shaderCode;
return shaderModule;
}
#endif
VkShaderModule loadShaderGLSL(const char *fileName, VkDevice device, VkShaderStageFlagBits stage)
{
std::string shaderSrc = readTextFile(fileName);
const char *shaderCode = shaderSrc.c_str();
size_t size = strlen(shaderCode);
assert(size > 0);
VkShaderModule shaderModule;
VkShaderModuleCreateInfo moduleCreateInfo;
moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
moduleCreateInfo.pNext = NULL;
moduleCreateInfo.codeSize = 3 * sizeof(uint32_t) + size + 1;
moduleCreateInfo.pCode = (uint32_t*)malloc(moduleCreateInfo.codeSize);
moduleCreateInfo.flags = 0;
// Magic SPV number
((uint32_t *)moduleCreateInfo.pCode)[0] = 0x07230203;
((uint32_t *)moduleCreateInfo.pCode)[1] = 0;
((uint32_t *)moduleCreateInfo.pCode)[2] = stage;
memcpy(((uint32_t *)moduleCreateInfo.pCode + 3), shaderCode, size + 1);
VK_CHECK_RESULT(vkCreateShaderModule(device, &moduleCreateInfo, NULL, &shaderModule));
return shaderModule;
}
VkImageMemoryBarrier prePresentBarrier(VkImage presentImage)
{
VkImageMemoryBarrier imageMemoryBarrier = vkTools::initializers::imageMemoryBarrier();
imageMemoryBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
imageMemoryBarrier.dstAccessMask = 0;
imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
imageMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
imageMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
imageMemoryBarrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
imageMemoryBarrier.image = presentImage;
return imageMemoryBarrier;
}
VkImageMemoryBarrier postPresentBarrier(VkImage presentImage)
{
VkImageMemoryBarrier imageMemoryBarrier = vkTools::initializers::imageMemoryBarrier();
imageMemoryBarrier.srcAccessMask = 0;
imageMemoryBarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
imageMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
imageMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
imageMemoryBarrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
imageMemoryBarrier.image = presentImage;
return imageMemoryBarrier;
}
void destroyUniformData(VkDevice device, vkTools::UniformData *uniformData)
{
if (uniformData->mapped != nullptr)
{
vkUnmapMemory(device, uniformData->memory);
}
vkDestroyBuffer(device, uniformData->buffer, nullptr);
vkFreeMemory(device, uniformData->memory, nullptr);
}
}
VkMemoryAllocateInfo vkTools::initializers::memoryAllocateInfo()
{
VkMemoryAllocateInfo memAllocInfo = {};
memAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
memAllocInfo.pNext = NULL;
memAllocInfo.allocationSize = 0;
memAllocInfo.memoryTypeIndex = 0;
return memAllocInfo;
}
VkCommandBufferAllocateInfo vkTools::initializers::commandBufferAllocateInfo(VkCommandPool commandPool, VkCommandBufferLevel level, uint32_t bufferCount)
{
VkCommandBufferAllocateInfo commandBufferAllocateInfo = {};
commandBufferAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
commandBufferAllocateInfo.commandPool = commandPool;
commandBufferAllocateInfo.level = level;
commandBufferAllocateInfo.commandBufferCount = bufferCount;
return commandBufferAllocateInfo;
}
VkCommandPoolCreateInfo vkTools::initializers::commandPoolCreateInfo()
{
VkCommandPoolCreateInfo cmdPoolCreateInfo = {};
cmdPoolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
return cmdPoolCreateInfo;
}
VkCommandBufferBeginInfo vkTools::initializers::commandBufferBeginInfo()
{
VkCommandBufferBeginInfo cmdBufferBeginInfo = {};
cmdBufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
cmdBufferBeginInfo.pNext = NULL;
return cmdBufferBeginInfo;
}
VkCommandBufferInheritanceInfo vkTools::initializers::commandBufferInheritanceInfo()
{
VkCommandBufferInheritanceInfo cmdBufferInheritanceInfo = {};
cmdBufferInheritanceInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO;
return cmdBufferInheritanceInfo;
}
VkRenderPassBeginInfo vkTools::initializers::renderPassBeginInfo()
{
VkRenderPassBeginInfo renderPassBeginInfo = {};
renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassBeginInfo.pNext = NULL;
return renderPassBeginInfo;
}
VkRenderPassCreateInfo vkTools::initializers::renderPassCreateInfo()
{
VkRenderPassCreateInfo renderPassCreateInfo = {};
renderPassCreateInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassCreateInfo.pNext = NULL;
return renderPassCreateInfo;
}
VkImageMemoryBarrier vkTools::initializers::imageMemoryBarrier()
{
VkImageMemoryBarrier imageMemoryBarrier = {};
imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
imageMemoryBarrier.pNext = NULL;
// Some default values
imageMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
imageMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
return imageMemoryBarrier;
}
VkBufferMemoryBarrier vkTools::initializers::bufferMemoryBarrier()
{
VkBufferMemoryBarrier bufferMemoryBarrier = {};
bufferMemoryBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
bufferMemoryBarrier.pNext = NULL;
return bufferMemoryBarrier;
}
VkMemoryBarrier vkTools::initializers::memoryBarrier()
{
VkMemoryBarrier memoryBarrier = {};
memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
memoryBarrier.pNext = NULL;
return memoryBarrier;
}
VkImageCreateInfo vkTools::initializers::imageCreateInfo()
{
VkImageCreateInfo imageCreateInfo = {};
imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageCreateInfo.pNext = NULL;
return imageCreateInfo;
}
VkSamplerCreateInfo vkTools::initializers::samplerCreateInfo()
{
VkSamplerCreateInfo samplerCreateInfo = {};
samplerCreateInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
samplerCreateInfo.pNext = NULL;
return samplerCreateInfo;
}
VkImageViewCreateInfo vkTools::initializers::imageViewCreateInfo()
{
VkImageViewCreateInfo imageViewCreateInfo = {};
imageViewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
imageViewCreateInfo.pNext = NULL;
return imageViewCreateInfo;
}
VkFramebufferCreateInfo vkTools::initializers::framebufferCreateInfo()
{
VkFramebufferCreateInfo framebufferCreateInfo = {};
framebufferCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
framebufferCreateInfo.pNext = NULL;
return framebufferCreateInfo;
}
VkSemaphoreCreateInfo vkTools::initializers::semaphoreCreateInfo()
{
VkSemaphoreCreateInfo semaphoreCreateInfo = {};
semaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
semaphoreCreateInfo.pNext = NULL;
semaphoreCreateInfo.flags = 0;
return semaphoreCreateInfo;
}
VkFenceCreateInfo vkTools::initializers::fenceCreateInfo(VkFenceCreateFlags flags)
{
VkFenceCreateInfo fenceCreateInfo = {};
fenceCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fenceCreateInfo.flags = flags;
return fenceCreateInfo;
}
VkEventCreateInfo vkTools::initializers::eventCreateInfo()
{
VkEventCreateInfo eventCreateInfo = {};
eventCreateInfo.sType = VK_STRUCTURE_TYPE_EVENT_CREATE_INFO;
return eventCreateInfo;
}
VkSubmitInfo vkTools::initializers::submitInfo()
{
VkSubmitInfo submitInfo = {};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.pNext = NULL;
return submitInfo;
}
VkViewport vkTools::initializers::viewport(
float width,
float height,
float minDepth,
float maxDepth)
{
VkViewport viewport = {};
viewport.width = width;
viewport.height = height;
viewport.minDepth = minDepth;
viewport.maxDepth = maxDepth;
return viewport;
}
VkRect2D vkTools::initializers::rect2D(
int32_t width,
int32_t height,
int32_t offsetX,
int32_t offsetY)
{
VkRect2D rect2D = {};
rect2D.extent.width = width;
rect2D.extent.height = height;
rect2D.offset.x = offsetX;
rect2D.offset.y = offsetY;
return rect2D;
}
VkBufferCreateInfo vkTools::initializers::bufferCreateInfo()
{
VkBufferCreateInfo bufCreateInfo = {};
bufCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
return bufCreateInfo;
}
VkBufferCreateInfo vkTools::initializers::bufferCreateInfo(
VkBufferUsageFlags usage,
VkDeviceSize size)
{
VkBufferCreateInfo bufCreateInfo = {};
bufCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufCreateInfo.pNext = NULL;
bufCreateInfo.usage = usage;
bufCreateInfo.size = size;
bufCreateInfo.flags = 0;
return bufCreateInfo;
}
VkDescriptorPoolCreateInfo vkTools::initializers::descriptorPoolCreateInfo(
uint32_t poolSizeCount,
VkDescriptorPoolSize* pPoolSizes,
uint32_t maxSets)
{
VkDescriptorPoolCreateInfo descriptorPoolInfo = {};
descriptorPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
descriptorPoolInfo.pNext = NULL;
descriptorPoolInfo.poolSizeCount = poolSizeCount;
descriptorPoolInfo.pPoolSizes = pPoolSizes;
descriptorPoolInfo.maxSets = maxSets;
return descriptorPoolInfo;
}
VkDescriptorPoolSize vkTools::initializers::descriptorPoolSize(
VkDescriptorType type,
uint32_t descriptorCount)
{
VkDescriptorPoolSize descriptorPoolSize = {};
descriptorPoolSize.type = type;
descriptorPoolSize.descriptorCount = descriptorCount;
return descriptorPoolSize;
}
VkDescriptorSetLayoutBinding vkTools::initializers::descriptorSetLayoutBinding(
VkDescriptorType type,
VkShaderStageFlags stageFlags,
uint32_t binding)
{
VkDescriptorSetLayoutBinding setLayoutBinding = {};
setLayoutBinding.descriptorType = type;
setLayoutBinding.stageFlags = stageFlags;
setLayoutBinding.binding = binding;
// Default value in all examples
setLayoutBinding.descriptorCount = 1;
return setLayoutBinding;
}
VkDescriptorSetLayoutCreateInfo vkTools::initializers::descriptorSetLayoutCreateInfo(
const VkDescriptorSetLayoutBinding* pBindings,
uint32_t bindingCount)
{
VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = {};
descriptorSetLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
descriptorSetLayoutCreateInfo.pNext = NULL;
descriptorSetLayoutCreateInfo.pBindings = pBindings;
descriptorSetLayoutCreateInfo.bindingCount = bindingCount;
return descriptorSetLayoutCreateInfo;
}
VkPipelineLayoutCreateInfo vkTools::initializers::pipelineLayoutCreateInfo(
const VkDescriptorSetLayout* pSetLayouts,
uint32_t setLayoutCount)
{
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = {};
pipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutCreateInfo.pNext = NULL;
pipelineLayoutCreateInfo.setLayoutCount = setLayoutCount;
pipelineLayoutCreateInfo.pSetLayouts = pSetLayouts;
return pipelineLayoutCreateInfo;
}
VkDescriptorSetAllocateInfo vkTools::initializers::descriptorSetAllocateInfo(
VkDescriptorPool descriptorPool,
const VkDescriptorSetLayout* pSetLayouts,
uint32_t descriptorSetCount)
{
VkDescriptorSetAllocateInfo descriptorSetAllocateInfo = {};
descriptorSetAllocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
descriptorSetAllocateInfo.pNext = NULL;
descriptorSetAllocateInfo.descriptorPool = descriptorPool;
descriptorSetAllocateInfo.pSetLayouts = pSetLayouts;
descriptorSetAllocateInfo.descriptorSetCount = descriptorSetCount;
return descriptorSetAllocateInfo;
}
VkDescriptorImageInfo vkTools::initializers::descriptorImageInfo(VkSampler sampler, VkImageView imageView, VkImageLayout imageLayout)
{
VkDescriptorImageInfo descriptorImageInfo = {};
descriptorImageInfo.sampler = sampler;
descriptorImageInfo.imageView = imageView;
descriptorImageInfo.imageLayout = imageLayout;
return descriptorImageInfo;
}
VkWriteDescriptorSet vkTools::initializers::writeDescriptorSet(
VkDescriptorSet dstSet,
VkDescriptorType type,
uint32_t binding,
VkDescriptorBufferInfo* bufferInfo)
{
VkWriteDescriptorSet writeDescriptorSet = {};
writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
writeDescriptorSet.pNext = NULL;
writeDescriptorSet.dstSet = dstSet;
writeDescriptorSet.descriptorType = type;
writeDescriptorSet.dstBinding = binding;
writeDescriptorSet.pBufferInfo = bufferInfo;
// Default value in all examples
writeDescriptorSet.descriptorCount = 1;
return writeDescriptorSet;
}
VkWriteDescriptorSet vkTools::initializers::writeDescriptorSet(
VkDescriptorSet dstSet,
VkDescriptorType type,
uint32_t binding,
VkDescriptorImageInfo * imageInfo)
{
VkWriteDescriptorSet writeDescriptorSet = {};
writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
writeDescriptorSet.pNext = NULL;
writeDescriptorSet.dstSet = dstSet;
writeDescriptorSet.descriptorType = type;
writeDescriptorSet.dstBinding = binding;
writeDescriptorSet.pImageInfo = imageInfo;
// Default value in all examples
writeDescriptorSet.descriptorCount = 1;
return writeDescriptorSet;
}
VkVertexInputBindingDescription vkTools::initializers::vertexInputBindingDescription(
uint32_t binding,
uint32_t stride,
VkVertexInputRate inputRate)
{
VkVertexInputBindingDescription vInputBindDescription = {};
vInputBindDescription.binding = binding;
vInputBindDescription.stride = stride;
vInputBindDescription.inputRate = inputRate;
return vInputBindDescription;
}
VkVertexInputAttributeDescription vkTools::initializers::vertexInputAttributeDescription(
uint32_t binding,
uint32_t location,
VkFormat format,
uint32_t offset)
{
VkVertexInputAttributeDescription vInputAttribDescription = {};
vInputAttribDescription.location = location;
vInputAttribDescription.binding = binding;
vInputAttribDescription.format = format;
vInputAttribDescription.offset = offset;
return vInputAttribDescription;
}
VkPipelineVertexInputStateCreateInfo vkTools::initializers::pipelineVertexInputStateCreateInfo()
{
VkPipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo = {};
pipelineVertexInputStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
pipelineVertexInputStateCreateInfo.pNext = NULL;
return pipelineVertexInputStateCreateInfo;
}
VkPipelineInputAssemblyStateCreateInfo vkTools::initializers::pipelineInputAssemblyStateCreateInfo(
VkPrimitiveTopology topology,
VkPipelineInputAssemblyStateCreateFlags flags,
VkBool32 primitiveRestartEnable)
{
VkPipelineInputAssemblyStateCreateInfo pipelineInputAssemblyStateCreateInfo = {};
pipelineInputAssemblyStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
pipelineInputAssemblyStateCreateInfo.topology = topology;
pipelineInputAssemblyStateCreateInfo.flags = flags;
pipelineInputAssemblyStateCreateInfo.primitiveRestartEnable = primitiveRestartEnable;
return pipelineInputAssemblyStateCreateInfo;
}
VkPipelineRasterizationStateCreateInfo vkTools::initializers::pipelineRasterizationStateCreateInfo(
VkPolygonMode polygonMode,
VkCullModeFlags cullMode,
VkFrontFace frontFace,
VkPipelineRasterizationStateCreateFlags flags)
{
VkPipelineRasterizationStateCreateInfo pipelineRasterizationStateCreateInfo = {};
pipelineRasterizationStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
pipelineRasterizationStateCreateInfo.polygonMode = polygonMode;
pipelineRasterizationStateCreateInfo.cullMode = cullMode;
pipelineRasterizationStateCreateInfo.frontFace = frontFace;
pipelineRasterizationStateCreateInfo.flags = flags;
pipelineRasterizationStateCreateInfo.depthClampEnable = VK_TRUE;
pipelineRasterizationStateCreateInfo.lineWidth = 1.0f;
return pipelineRasterizationStateCreateInfo;
}
VkPipelineColorBlendAttachmentState vkTools::initializers::pipelineColorBlendAttachmentState(
VkColorComponentFlags colorWriteMask,
VkBool32 blendEnable)
{
VkPipelineColorBlendAttachmentState pipelineColorBlendAttachmentState = {};
pipelineColorBlendAttachmentState.colorWriteMask = colorWriteMask;
pipelineColorBlendAttachmentState.blendEnable = blendEnable;
return pipelineColorBlendAttachmentState;
}
VkPipelineColorBlendStateCreateInfo vkTools::initializers::pipelineColorBlendStateCreateInfo(
uint32_t attachmentCount,
const VkPipelineColorBlendAttachmentState * pAttachments)
{
VkPipelineColorBlendStateCreateInfo pipelineColorBlendStateCreateInfo = {};
pipelineColorBlendStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
pipelineColorBlendStateCreateInfo.pNext = NULL;
pipelineColorBlendStateCreateInfo.attachmentCount = attachmentCount;
pipelineColorBlendStateCreateInfo.pAttachments = pAttachments;
return pipelineColorBlendStateCreateInfo;
}
VkPipelineDepthStencilStateCreateInfo vkTools::initializers::pipelineDepthStencilStateCreateInfo(
VkBool32 depthTestEnable,
VkBool32 depthWriteEnable,
VkCompareOp depthCompareOp)
{
VkPipelineDepthStencilStateCreateInfo pipelineDepthStencilStateCreateInfo = {};
pipelineDepthStencilStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
pipelineDepthStencilStateCreateInfo.depthTestEnable = depthTestEnable;
pipelineDepthStencilStateCreateInfo.depthWriteEnable = depthWriteEnable;
pipelineDepthStencilStateCreateInfo.depthCompareOp = depthCompareOp;
pipelineDepthStencilStateCreateInfo.front = pipelineDepthStencilStateCreateInfo.back;
pipelineDepthStencilStateCreateInfo.back.compareOp = VK_COMPARE_OP_ALWAYS;
return pipelineDepthStencilStateCreateInfo;
}
VkPipelineViewportStateCreateInfo vkTools::initializers::pipelineViewportStateCreateInfo(
uint32_t viewportCount,
uint32_t scissorCount,
VkPipelineViewportStateCreateFlags flags)
{
VkPipelineViewportStateCreateInfo pipelineViewportStateCreateInfo = {};
pipelineViewportStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
pipelineViewportStateCreateInfo.viewportCount = viewportCount;
pipelineViewportStateCreateInfo.scissorCount = scissorCount;
pipelineViewportStateCreateInfo.flags = flags;
return pipelineViewportStateCreateInfo;
}
VkPipelineMultisampleStateCreateInfo vkTools::initializers::pipelineMultisampleStateCreateInfo(
VkSampleCountFlagBits rasterizationSamples,
VkPipelineMultisampleStateCreateFlags flags)
{
VkPipelineMultisampleStateCreateInfo pipelineMultisampleStateCreateInfo = {};
pipelineMultisampleStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
pipelineMultisampleStateCreateInfo.rasterizationSamples = rasterizationSamples;
pipelineMultisampleStateCreateInfo.flags = flags;
return pipelineMultisampleStateCreateInfo;
}
VkPipelineDynamicStateCreateInfo vkTools::initializers::pipelineDynamicStateCreateInfo(
const VkDynamicState * pDynamicStates,
uint32_t dynamicStateCount,
VkPipelineDynamicStateCreateFlags flags)
{
VkPipelineDynamicStateCreateInfo pipelineDynamicStateCreateInfo = {};
pipelineDynamicStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
pipelineDynamicStateCreateInfo.pDynamicStates = pDynamicStates;
pipelineDynamicStateCreateInfo.dynamicStateCount = dynamicStateCount;
pipelineDynamicStateCreateInfo.flags = flags;
return pipelineDynamicStateCreateInfo;
}
VkPipelineTessellationStateCreateInfo vkTools::initializers::pipelineTessellationStateCreateInfo(uint32_t patchControlPoints)
{
VkPipelineTessellationStateCreateInfo pipelineTessellationStateCreateInfo = {};
pipelineTessellationStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO;
pipelineTessellationStateCreateInfo.patchControlPoints = patchControlPoints;
return pipelineTessellationStateCreateInfo;
}
VkGraphicsPipelineCreateInfo vkTools::initializers::pipelineCreateInfo(
VkPipelineLayout layout,
VkRenderPass renderPass,
VkPipelineCreateFlags flags)
{
VkGraphicsPipelineCreateInfo pipelineCreateInfo = {};
pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipelineCreateInfo.pNext = NULL;
pipelineCreateInfo.layout = layout;
pipelineCreateInfo.renderPass = renderPass;
pipelineCreateInfo.flags = flags;
return pipelineCreateInfo;
}
VkComputePipelineCreateInfo vkTools::initializers::computePipelineCreateInfo(VkPipelineLayout layout, VkPipelineCreateFlags flags)
{
VkComputePipelineCreateInfo computePipelineCreateInfo = {};
computePipelineCreateInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
computePipelineCreateInfo.layout = layout;
computePipelineCreateInfo.flags = flags;
return computePipelineCreateInfo;
}
VkPushConstantRange vkTools::initializers::pushConstantRange(
VkShaderStageFlags stageFlags,
uint32_t size,
uint32_t offset)
{
VkPushConstantRange pushConstantRange = {};
pushConstantRange.stageFlags = stageFlags;
pushConstantRange.offset = offset;
pushConstantRange.size = size;
return pushConstantRange;
}
// vi: set sw=2 ts=4 expandtab:
@@ -0,0 +1,312 @@
/*
* Copyright 2016 Sascha Willems - www.saschawillems.de
* SPDX-License-Identifier: MIT
*
* Assorted commonly used Vulkan helper functions
*/
#pragma once
#include "vulkan/vulkan.h"
#include <math.h>
#include <stdlib.h>
#include <string>
#include <cstring>
#include <fstream>
#include <assert.h>
#include <stdio.h>
#include <vector>
#include <iostream>
#include <stdexcept>
#if defined(_WIN32)
#include <windows.h>
#include <fcntl.h>
#include <io.h>
#elif defined(__ANDROID__)
#include "vulkanandroid.h"
#include <android/asset_manager.h>
#endif
#include <SDL3/SDL_messagebox.h>
// Custom define for better code readability
#define VK_FLAGS_NONE 0
// Default fence timeout in nanoseconds
#define DEFAULT_FENCE_TIMEOUT 100000000000
#if 0
// Macro to check and display Vulkan return results
#define VK_CHECK_RESULT(f) \
{ \
VkResult res = (f); \
if (res != VK_SUCCESS) \
{ \
std::cout << "Fatal : VkResult is \"" << vkTools::errorString(res) << "\" in " << __FILE__ << " at line " << __LINE__ << std::endl; \
assert(res == VK_SUCCESS); \
} \
}
#endif
#if defined(DEBUG)
#include <sstream>
extern const char* appName();
#define VK_CHECK_RESULT(f) \
{ \
VkResult res = (f); \
if (res != VK_SUCCESS) \
{ \
std::stringstream msg; \
msg << "Fatal error. VkResult is \"" \
<< vkTools::errorString(res) << "\" in " << __FILE__ \
<< " at line " << __LINE__ << std::endl; \
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, \
appName(), \
msg.str().c_str(), \
NULL); \
assert(res == VK_SUCCESS); \
} \
}
#else
#define VK_CHECK_RESULT(f) (void)f
#endif
namespace vkTools
{
// Check if extension is globally available
VkBool32 checkGlobalExtensionPresent(const char* extensionName);
// Check if extension is present on the given device
VkBool32 checkDeviceExtensionPresent(VkPhysicalDevice physicalDevice, const char* extensionName);
// Return string representation of a vulkan error string
std::string errorString(VkResult errorCode);
// Selected a suitable supported depth format starting with 32 bit down to 16 bit
// Returns false if none of the depth formats in the list is supported by the device
VkBool32 getSupportedDepthFormat(VkPhysicalDevice physicalDevice, VkFormat *depthFormat);
// Put an image memory barrier for setting an image layout on the sub resource into the given command buffer
void setImageLayout(
VkCommandBuffer cmdbuffer,
VkImage image,
VkImageAspectFlags aspectMask,
VkImageLayout oldImageLayout,
VkImageLayout newImageLayout,
VkImageSubresourceRange subresourceRange);
// Uses a fixed sub resource layout with first mip level and layer
void setImageLayout(
VkCommandBuffer cmdbuffer,
VkImage image,
VkImageAspectFlags aspectMask,
VkImageLayout oldImageLayout,
VkImageLayout newImageLayout);
// Display error message and exit on fatal error
void exitFatal(std::string message, std::string caption);
// Load a text file (e.g. GLGL shader) into a std::string
std::string readTextFile(const char *fileName);
// Load a binary file into a buffer (e.g. SPIR-V)
char *readBinaryFile(const char *filename, size_t *psize);
// Load a SPIR-V shader
#if defined(__ANDROID__)
VkShaderModule loadShader(AAssetManager* assetManager, const char *fileName, VkDevice device, VkShaderStageFlagBits stage);
#else
VkShaderModule loadShader(const char *fileName, VkDevice device, VkShaderStageFlagBits stage);
#endif
// Load a GLSL shader
// Note : Only for testing purposes, support for directly feeding GLSL shaders into Vulkan
// may be dropped at some point
VkShaderModule loadShaderGLSL(const char *fileName, VkDevice device, VkShaderStageFlagBits stage);
// Returns a pre-present image memory barrier
// Transforms the image's layout from color attachment to present khr
VkImageMemoryBarrier prePresentBarrier(VkImage presentImage);
// Returns a post-present image memory barrier
// Transforms the image's layout back from present khr to color attachment
VkImageMemoryBarrier postPresentBarrier(VkImage presentImage);
// Contains all vulkan objects
// required for a uniform data object
struct UniformData
{
VkBuffer buffer;
VkDeviceMemory memory;
VkDescriptorBufferInfo descriptor;
uint32_t allocSize;
void* mapped = nullptr;
};
// Destroy (and free) Vulkan resources used by a uniform data structure
void destroyUniformData(VkDevice device, vkTools::UniformData *uniformData);
// Contains often used vulkan object initializers
// Save lot of VK_STRUCTURE_TYPE assignments
// Some initializers are parameterized for convenience
namespace initializers
{
VkMemoryAllocateInfo memoryAllocateInfo();
VkCommandBufferAllocateInfo commandBufferAllocateInfo(
VkCommandPool commandPool,
VkCommandBufferLevel level,
uint32_t bufferCount);
VkCommandPoolCreateInfo commandPoolCreateInfo();
VkCommandBufferBeginInfo commandBufferBeginInfo();
VkCommandBufferInheritanceInfo commandBufferInheritanceInfo();
VkRenderPassBeginInfo renderPassBeginInfo();
VkRenderPassCreateInfo renderPassCreateInfo();
VkImageMemoryBarrier imageMemoryBarrier();
VkBufferMemoryBarrier bufferMemoryBarrier();
VkMemoryBarrier memoryBarrier();
VkImageCreateInfo imageCreateInfo();
VkSamplerCreateInfo samplerCreateInfo();
VkImageViewCreateInfo imageViewCreateInfo();
VkFramebufferCreateInfo framebufferCreateInfo();
VkSemaphoreCreateInfo semaphoreCreateInfo();
VkFenceCreateInfo fenceCreateInfo(VkFenceCreateFlags flags);
VkEventCreateInfo eventCreateInfo();
VkSubmitInfo submitInfo();
VkViewport viewport(
float width,
float height,
float minDepth,
float maxDepth);
VkRect2D rect2D(
int32_t width,
int32_t height,
int32_t offsetX,
int32_t offsetY);
VkBufferCreateInfo bufferCreateInfo();
VkBufferCreateInfo bufferCreateInfo(
VkBufferUsageFlags usage,
VkDeviceSize size);
VkDescriptorPoolCreateInfo descriptorPoolCreateInfo(
uint32_t poolSizeCount,
VkDescriptorPoolSize* pPoolSizes,
uint32_t maxSets);
VkDescriptorPoolSize descriptorPoolSize(
VkDescriptorType type,
uint32_t descriptorCount);
VkDescriptorSetLayoutBinding descriptorSetLayoutBinding(
VkDescriptorType type,
VkShaderStageFlags stageFlags,
uint32_t binding);
VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo(
const VkDescriptorSetLayoutBinding* pBindings,
uint32_t bindingCount);
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo(
const VkDescriptorSetLayout* pSetLayouts,
uint32_t setLayoutCount );
VkDescriptorSetAllocateInfo descriptorSetAllocateInfo(
VkDescriptorPool descriptorPool,
const VkDescriptorSetLayout* pSetLayouts,
uint32_t descriptorSetCount);
VkDescriptorImageInfo descriptorImageInfo(
VkSampler sampler,
VkImageView imageView,
VkImageLayout imageLayout);
VkWriteDescriptorSet writeDescriptorSet(
VkDescriptorSet dstSet,
VkDescriptorType type,
uint32_t binding,
VkDescriptorBufferInfo* bufferInfo);
VkWriteDescriptorSet writeDescriptorSet(
VkDescriptorSet dstSet,
VkDescriptorType type,
uint32_t binding,
VkDescriptorImageInfo* imageInfo);
VkVertexInputBindingDescription vertexInputBindingDescription(
uint32_t binding,
uint32_t stride,
VkVertexInputRate inputRate);
VkVertexInputAttributeDescription vertexInputAttributeDescription(
uint32_t binding,
uint32_t location,
VkFormat format,
uint32_t offset);
VkPipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo();
VkPipelineInputAssemblyStateCreateInfo pipelineInputAssemblyStateCreateInfo(
VkPrimitiveTopology topology,
VkPipelineInputAssemblyStateCreateFlags flags,
VkBool32 primitiveRestartEnable);
VkPipelineRasterizationStateCreateInfo pipelineRasterizationStateCreateInfo(
VkPolygonMode polygonMode,
VkCullModeFlags cullMode,
VkFrontFace frontFace,
VkPipelineRasterizationStateCreateFlags flags);
VkPipelineColorBlendAttachmentState pipelineColorBlendAttachmentState(
VkColorComponentFlags colorWriteMask,
VkBool32 blendEnable);
VkPipelineColorBlendStateCreateInfo pipelineColorBlendStateCreateInfo(
uint32_t attachmentCount,
const VkPipelineColorBlendAttachmentState* pAttachments);
VkPipelineDepthStencilStateCreateInfo pipelineDepthStencilStateCreateInfo(
VkBool32 depthTestEnable,
VkBool32 depthWriteEnable,
VkCompareOp depthCompareOp);
VkPipelineViewportStateCreateInfo pipelineViewportStateCreateInfo(
uint32_t viewportCount,
uint32_t scissorCount,
VkPipelineViewportStateCreateFlags flags);
VkPipelineMultisampleStateCreateInfo pipelineMultisampleStateCreateInfo(
VkSampleCountFlagBits rasterizationSamples,
VkPipelineMultisampleStateCreateFlags flags);
VkPipelineDynamicStateCreateInfo pipelineDynamicStateCreateInfo(
const VkDynamicState *pDynamicStates,
uint32_t dynamicStateCount,
VkPipelineDynamicStateCreateFlags flags);
VkPipelineTessellationStateCreateInfo pipelineTessellationStateCreateInfo(
uint32_t patchControlPoints);
VkGraphicsPipelineCreateInfo pipelineCreateInfo(
VkPipelineLayout layout,
VkRenderPass renderPass,
VkPipelineCreateFlags flags);
VkComputePipelineCreateInfo computePipelineCreateInfo(
VkPipelineLayout layout,
VkPipelineCreateFlags flags);
VkPushConstantRange pushConstantRange(
VkShaderStageFlags stageFlags,
uint32_t size,
uint32_t offset);
}
}
// vi: set sw=2 ts=4 expandtab:
+96
View File
@@ -0,0 +1,96 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/* $Id: f63e0a9e6eed51ed84a8eea1eff0708c8a6af22b $ */
/*
* Copyright 2015-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief main() function for SDL app framework.
*/
#include <string>
#include <vector>
#include <stdio.h>
#include "AppBaseSDL.h"
#include <SDL3/SDL_main.h>
#include "platform_utils.h"
#if defined(EMSCRIPTEN)
#include <emscripten.h>
#endif
#define SDL_GESTURE_IMPLEMENTATION 1
#include "SDL_gesture.h"
#if defined(SDL_PLATFORM_IOS)
#define NEED_MAIN_LOOP 0
//int SDL_iPhoneSetAnimationCallback(
// SDL_Window * window, int interval,
// void (*callback)(void*), void *callbackParam
// );
#define setAnimationCallback(win, cb, userdata) \
SDL_SetiOSAnimationCallback(win, 1, cb, userdata)
#elif defined(EMSCRIPTEN)
#define NEED_MAIN_LOOP 0
//void emscripten_set_main_loop_arg(em_arg_callback_func func, void *arg,
// int fps, int simulate_infinite_loop);
#define setAnimationCallback(win, cb, userdata) \
emscripten_set_main_loop_arg(cb, userdata, 0, 0)
#else
#define NEED_MAIN_LOOP 1
#define setAnimationCallback(win, cb, userdata)
#endif
static void
quit() {
Gesture_Quit();
SDL_Quit();
}
int
main(int argc, char* argv[])
{
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) {
fprintf(stderr, "%s: SDL video initialization failed: %s\n",
theApp->name(), SDL_GetError());
return 1;
}
Gesture_Init();
atexit(quit);
InitUTF8CLI(argc, argv);
AppBaseSDL::Args args(argv, argv+argc);
if (!theApp->initialize(args))
return 1;
// Catches events before they are added to the event queue.
// May need this for some events that need rapid response...
// SDL_SetEventFilter(theApp->onEvent, theApp);
// Triggered when event added to queue.
SDL_AddEventWatch(theApp->onEvent, theApp);
if (!NEED_MAIN_LOOP) {
// TODO: Fix this main to work for multiple windows. One way is to have the
// application call setAnimationCallback and keep a list of the windows in
// this file, calling drawFrame for each window.
setAnimationCallback(theApp->getMainWindow(), theApp->onDrawFrame, theApp);
// iOS version of SDL will not exit when main completes.
// The Emscripten version of the app must be compiled with
// -s NO_EXIT_RUNTIME=1 to prevent Emscripten exiting when main completes.
return 0;
} else {
for (;;) {
SDL_PumpEvents();
theApp->drawFrame();
// XXX Let app return a sleeptime from drawFrame()? If so
// sleep(sleeptime);
}
}
}
@@ -0,0 +1,330 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2017-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @class LoadTestSample
* @~English
*
* @brief Definition of a base class for texture loading test samples.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#if defined(_WIN32)
#define _USE_MATH_DEFINES
#endif
#include "LoadTestSample.h"
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/vector_angle.hpp>
#include <SDL3/SDL_log.h>
#if !defined(LOADTESTSAMPLE_LOG_GESTURE_DETECTION)
// Log detected and completed gestures.
#define LOADTESTSAMPLE_LOG_GESTURE_DETECTION 0
#endif
#if !defined(LOADTESTSAMPLE_LOG_GESTURE_EVENTS)
// Log events contributing to gesture detection and gestures.
#define LOADTESTSAMPLE_LOG_GESTURE_EVENTS 0
#endif
#if !defined(LOADTESTSAMPLE_LOG_MOUSE_UP_DOWN_EVENTS)
#define LOADTESTSAMPLE_LOG_MOUSE_UP_DOWN_EVENTS 0
#endif
#if !defined(LOADTESTSAMPLE_LOG_MOUSE_MOTION_EVENTS)
#define LOADTESTSAMPLE_LOG_MOUSE_MOTION_EVENTS 0
#endif
#if LOADTESTSAMPLE_LOG_GESTURE_EVENTS
#include <sstream>
const std::string printFingerIds(SDL_Finger* fingers[], uint32_t numFingers) {
std::stringstream msg;
assert(numFingers > 0);
msg << std::hex << std::showbase;
msg << "finger id" << (numFingers > 1 ? "s" : "") << ": ";
for (uint32_t f = 0; f < numFingers; f++) {
if (f > 0) {
if (f == numFingers - 1)
msg << " & ";
else
msg << ", ";
}
msg << fingers[f]->id;
}
return msg.str();
}
const std::string printVector(const std::string& name, glm::vec2 v) {
std::stringstream msg;
msg << name << " (" << v.x << ", " << v.y << ")";
return msg.str();
}
#endif
[[maybe_unused]] static const char*
buttonName(Uint8 button) {
switch(button) {
case SDL_BUTTON_LEFT: return "left";
case SDL_BUTTON_MIDDLE: return "middle";
case SDL_BUTTON_RIGHT: return "right";
default: return "other";
}
}
int
LoadTestSample::doEvent(SDL_Event* event)
{
switch (event->type) {
case SDL_EVENT_MOUSE_MOTION:
{
SDL_MouseMotionEvent& motion = event->motion;
#if LOADTESTSAMPLE_LOG_MOUSE_MOTION_EVENTS
SDL_Log("LTS: MOUSE_MOTION - x: %f, y: %f", motion.x, motion.y);
#endif
// On macOS with trackpad, SDL_TOUCH_MOUSEID is never set.
// Prefer mouse events on macOS because press is required. When
// finger motion events are used the object starts to rotate when
// you drag the cursor over the window. Not nice.
if (mouseButtons.left)
{
rotation.x -= yflip * (mousePos.y - (float)motion.y) * 1.25f;
rotation.y -= (mousePos.x - (float)motion.x) * 1.25f;
viewChanged();
}
if (mouseButtons.right)
{
zoom += (mousePos.y - (float)motion.y) * .005f;
viewChanged();
}
if (mouseButtons.middle)
{
cameraPos.x -= (mousePos.x - (float)motion.x) * 0.01f;
cameraPos.y += yflip * (mousePos.y - (float)motion.y) * 0.01f;
viewChanged();
}
mousePos = glm::vec2((float)motion.x, (float)motion.y);
return 0;
}
case SDL_EVENT_MOUSE_BUTTON_DOWN:
mousePos = glm::vec2((float)event->button.x, (float)event->button.y);
if (LOADTESTSAMPLE_LOG_MOUSE_UP_DOWN_EVENTS) {
SDL_Log("LTS: MOUSE_DOWN - button: %s, x: %f, y: %f", buttonName(event->button.button),
event->button.x, event->button.y);
}
switch (event->button.button) {
case SDL_BUTTON_LEFT:
mouseButtons.left = true;
break;
case SDL_BUTTON_MIDDLE:
mouseButtons.middle = true;
break;
case SDL_BUTTON_RIGHT:
mouseButtons.right = true;
break;
default:
return 1;
}
return 0;
case SDL_EVENT_MOUSE_BUTTON_UP:
if (LOADTESTSAMPLE_LOG_MOUSE_UP_DOWN_EVENTS) {
SDL_Log("LTS: MOUSE_UP - button: %s, x: %f, y: %f", buttonName(event->button.button),
event->button.x, event->button.y);
}
switch (event->button.button) {
case SDL_BUTTON_LEFT:
mouseButtons.left = false;
break;
case SDL_BUTTON_MIDDLE:
mouseButtons.middle = false;
break;
case SDL_BUTTON_RIGHT:
mouseButtons.right = false;
break;
default:
return 1;
}
return 0;
case SDL_EVENT_FINGER_DOWN: {
// Prevent multifingers from triggering the left button action and
// interfering with multigestures.
//
// On iOS you get a left button down event no matter how many fingers
// you touch to the screen. We want 1 finger mouse to work so
// behaviour is same as pressing the trackpad on macOS, etc. As iOS
// button_down events come before finger_down we can clear the left
// button down state, if we have multiple fingers. Hope this ordering
// is the same on other touch screen platforms that send a left-button
// event regardless of the number of fingers.
//
// On macOS button_down events come after finger_down so this code has
// no effect.
//
// Another way to handle this is to identify the platform and work
// differently for each platform.
int numFingers;
SDL_Finger** fingers = SDL_GetTouchFingers(event->tfinger.touchID, &numFingers);
int retVal = 0;
#if LOADTESTSAMPLE_LOG_GESTURE_EVENTS
SDL_Log("LTS: Finger: %#" SDL_PRIx64 " down - fingers: %i, %s, x: %f, y: %f",
event->tfinger.fingerID, numFingers,
printFingerIds(fingers, numFingers).c_str(),
event->tfinger.x, event->tfinger.y);
#endif
if (numFingers > 1) {
mouseButtons.left = false;
if (LOADTESTSAMPLE_LOG_GESTURE_EVENTS) {
SDL_Log("LTS: FINGER_DOWN with multiple fingers received."
" Resetting mouseButtons.left.");
}
if (numFingers == 2) {
firstFingerId = fingers[0]->id;
// Calc. difference vector between fingers.
glm::vec2 vDifference;
vDifference.x = fingers[1]->x - fingers[0]->x;
vDifference.y = fingers[1]->y - fingers[0]->y;
distanceStart = glm::length(vDifference);
distanceLast = distanceStart;
// Need normalized vectors for glm::orientedAngle
nvDifferenceStart = glm::normalize(vDifference);
nvDifferenceLast = nvDifferenceStart;
processingGesture = true;
#if LOADTESTSAMPLE_LOG_GESTURE_EVENTS
// Angle of vector to X axis.
xAngleStart = atan2f(vDifference.y, vDifference.x);
SDL_Log("LTS: FINGER_DOWN, start values: %s, Distance = %f, XAngle = %f°",
printVector("Difference", vDifference).c_str(),
distanceStart, xAngleStart * 180.0 / M_PI
);
#endif
retVal = 1;
}
}
// It is possible to somehow get out of the window without seeing
// FINGER_UP so as a safeguard stop any previous gesture.
zooming = rotating = false;
SDL_free(fingers);
return retVal;
}
case SDL_EVENT_FINGER_UP: {
int numFingers;
SDL_Finger** fingers = SDL_GetTouchFingers(event->tfinger.touchID, &numFingers);
#if LOADTESTSAMPLE_LOG_GESTURE_EVENTS
SDL_Log("LTS: Finger: %#" SDL_PRIx64 " up - fingers: %i, %s, x: %f, y: %f",
event->tfinger.fingerID, numFingers,
printFingerIds(fingers, numFingers).c_str(),
event->tfinger.x, event->tfinger.y);
#endif
if (processingGesture && numFingers == 2) {
// There may still be one finger down. Even so the action is completed.
if (LOADTESTSAMPLE_LOG_GESTURE_DETECTION) {
SDL_Log("-------------- LTS: %s complete. -----------------",
zooming ? "zooming" : rotating ? "rotating" : "gesture");
}
zooming = rotating = processingGesture = false;
}
SDL_free(fingers);
break;
}
case SDL_EVENT_FINGER_MOTION: {
int numFingers;
SDL_Finger** fingers = SDL_GetTouchFingers(event->tfinger.touchID, &numFingers);
if (numFingers != 2)
return 1;
if (!processingGesture) {
// Protect against FINGER_MOTION without FINGER_DOWN. This can
// happen when the sample is switched by a swipe and the new sample
// receives the tail end of the swipe motion.
return 1;
}
// With two fingers down, events come in pairs. No point in processing
// both.
if (event->tfinger.fingerID == firstFingerId) {
return 0;
}
glm::vec2 vDifference; // Difference vector between the fingers.
vDifference.x = fingers[1]->x - fingers[0]->x;
vDifference.y = fingers[1]->y - fingers[0]->y;
float distance = glm::length(vDifference);
// Normalized vectors required by glm::orientedAngle
glm::vec2 nvDifference = glm::normalize(vDifference);
// Angle between start and current difference vectors
float sAngle = glm::orientedAngle(nvDifferenceStart, nvDifference);
// Angle between current and previous difference vectors
float dAngle = glm::orientedAngle(nvDifferenceLast, nvDifference);
// Difference in distance since last motion event.
float dDist = distance - distanceLast;
// Difference in distance since start.
float dDistStart = distance - distanceStart;
#if LOADTESTSAMPLE_LOG_GESTURE_EVENTS
if (!(rotating || zooming)) {
// Angle from X axis to vDifference vector
float xAngle = atan2f(vDifference.y, vDifference.x);
SDL_Log("LTS FINGER_MOTION: Not zooming or rotating. "
" timestamp = %" SDL_PRIu64 ", %s, %s",
event->tfinger.timestamp,
printFingerIds(fingers, numFingers).c_str(),
printVector("Difference", vDifference).c_str());
SDL_Log("... distanceLast = %f, distance = %f, dDist = %f, dDistStart = %f, xAngle = %f°, sAngle = %f°, dAngle = %f°",
distanceLast, distance, dDist, dDistStart,
xAngle * 180.0 / M_PI, sAngle * 180.0 / M_PI, dAngle * 180.0 / M_PI);
}
#endif
nvDifferenceLast = nvDifference;
distanceLast = distance;
// This is all heuristics derived from use.
if (zooming) {
zoom += dDist * 10.0f;
if (LOADTESTSAMPLE_LOG_GESTURE_EVENTS) {
SDL_Log("LTS MG: Zooming. zoom = %f", zoom);
}
} else if (!rotating) {
if (fabs(dDistStart) >= 0.1 && fabs(dAngle) < 0.5 * M_PI / 180.0) {
zooming = true;
zoom += dDist * 10.0f;
if (LOADTESTSAMPLE_LOG_GESTURE_DETECTION) {
SDL_Log("---------------- LTS MG: pinch/zoom detected ---------------\n"
" dAngle = %f°, dDistStart = %f, dDist = %f, zoom = %f",
dAngle * 180.0 / M_PI, dDistStart, dDist, zoom);
}
}
}
if (rotating) {
rotation.z +=
static_cast<float>(dAngle * 180.0 / M_PI);
if (LOADTESTSAMPLE_LOG_GESTURE_EVENTS) {
SDL_Log("LTS MG: Rotating around Z. rotation.z = %f°", rotation.z);
}
} else if (!zooming) {
if (fabs(sAngle) > 15 * M_PI / 180.0 && fabs(dDistStart) < 0.1) {
rotating = true;
rotation.z += static_cast<float>(dAngle * 180.0 / M_PI);
if (LOADTESTSAMPLE_LOG_GESTURE_DETECTION) {
SDL_Log("---------------- LTS MG: rotation detected ---------------\n"
" sAngle = %f°, dAngle = %f°, dDistStart = %f, rotation.z = %f°",
sAngle * 180 / M_PI, dAngle * 180.0 / M_PI, dDistStart, rotation.z);
}
}
}
viewChanged();
SDL_free(fingers);
return 0;
}
case SDL_EVENT_KEY_UP:
if (event->key.key == 'q')
quit = true;
keyPressed(event->key.key);
return 0;
default:
break;
}
return 1;
}
@@ -0,0 +1,91 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2017-2020 Mark Callow, <khronos at callow dot im>.
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _LOAD_TEST_SAMPLE_H
#define _LOAD_TEST_SAMPLE_H
#include <string>
#include <SDL3/SDL.h>
#define GLM_FORCE_RADIANS
#include "disable_glm_warnings.h"
#include <glm/glm.hpp>
#include "reenable_warnings.h"
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
class LoadTestSample {
public:
typedef uint64_t ticks_t;
LoadTestSample(uint32_t width, uint32_t height,
const std::string sBasePath,
int32_t yflip = 1)
: w_width(width), w_height(height), yflip(yflip),
sBasePath(sBasePath)
{
}
virtual ~LoadTestSample() { };
virtual int doEvent(SDL_Event* event);
virtual void resize(uint32_t width, uint32_t height) = 0;
virtual void run(uint32_t msTicks) = 0;
//virtual void getOverlayText(TextOverlay *textOverlay) { };
typedef LoadTestSample* (*PFN_create)(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath);
protected:
virtual void keyPressed(uint32_t /*keyCode*/) { }
virtual void viewChanged() { }
const std::string getAssetPath() { return sBasePath; }
glm::vec3 rotation;
glm::vec3 cameraPos;
glm::vec2 mousePos;
glm::vec2 nvDifferenceStart; // Normalized difference between fingers at start of gesture.
float distanceStart = 0.0; // Distance between fingers at start of gesture.
float xAngleStart = 0.0; // Angle between x-axis and nvDifferenceStart. Unused unless event logging is enabled.
glm::vec2 nvDifferenceLast; // Normalized difference between fingers at last motion event.
float distanceLast = 0.0; // Distance between fingers at last motion event.
Uint64 firstFingerId = 0;
bool processingGesture = false;
struct {
bool left = false;
bool right = false;
bool middle = false;
} mouseButtons;
bool quit = false;
bool rotating = false;
bool zooming = false;
bool paused = false;
float zoom = 0;
uint32_t w_width;
uint32_t w_height;
// Defines a frame rate independent timer value clamped from -1.0...1.0
// For use in animations, rotations, etc.
float timer = 0.0f;
// Multiplier for speeding up (or slowing down) the global timer
float timerSpeed = 0.25f;
// Use to adjust mouse rotation speed
float rotationSpeed = 1.0f;
// Use to adjust mouse zoom speed
float zoomSpeed = 1.0f;
// multiplier to decide if Y increases down or up
int32_t yflip;
const std::string sBasePath;
};
#endif /* _LOAD_TEST_SAMPLE_H */
@@ -0,0 +1,146 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2017-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @class SwipeDetector
* @~English
*
* @brief Definition of a class for detecting swipes.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#include <assert.h>
#include "SwipeDetector.h"
#include <SDL3/SDL_log.h>
#if !defined(SWIPEDETECTOR_LOG_GESTURE_EVENTS)
#define SWIPEDETECTOR_LOG_GESTURE_EVENTS 0
#endif
#if !defined(SWIPEDETECTOR_LOG_GESTURE_DETECTION)
#define SWIPEDETECTOR_LOG_GESTURE_DETECTION 0
#endif
bool
SwipeDetector::doEvent(SDL_Event* event)
{
bool result = false;
switch (event->type) {
case SDL_EVENT_FINGER_UP: {
int numFingers;
SDL_Finger** fingers = SDL_GetTouchFingers(event->tfinger.touchID, &numFingers);
if (SWIPEDETECTOR_LOG_GESTURE_EVENTS) {
SDL_Log("SD: Finger: %" SDL_PRIx64 " UP - fingers: %i, x: %f, y: %f",
event->tfinger.fingerID, numFingers, event->tfinger.x, event->tfinger.y);
}
// SDL_GetTouchFingers appears to return the number of fingers
// down *before* the event was generated, so 1 means the last finger
// just lifted.
if (numFingers == 1 && gestureStart.time != 0) {
gestureStart.time = 0;
gestureSwipe = false;
if (SWIPEDETECTOR_LOG_GESTURE_DETECTION) {
SDL_Log("***************** SD: FINGER_UP, %smultigesture done *****************",
gestureSwipe ? "Swipe complete & " : "");
}
} else {
result = true;
}
SDL_free(fingers);
break;
}
case GESTURE_MULTIGESTURE: {
Gesture_MultiGestureEvent& mgesture = *(Gesture_MultiGestureEvent *)event;
if (SWIPEDETECTOR_LOG_GESTURE_EVENTS) {
SDL_Log("SD: MG Event: x = %f, y = %f, dAng = %f (%f), dR = %f, numFingers = %i, time = %" SDL_PRIu64,
mgesture.x,
mgesture.y,
mgesture.dTheta * 180.0 / M_PI,
mgesture.dTheta,
mgesture.dDist,
mgesture.numFingers,
mgesture.timestamp);
}
if (SWIPEDETECTOR_LOG_GESTURE_DETECTION) {
SDL_Log("SD: mgestureSwipe = %i, time = %" SDL_PRIu64,
gestureSwipe,
(mgesture.timestamp - gestureStart.time) / 1000000);
}
if (gestureStart.time == 0) {
if (SWIPEDETECTOR_LOG_GESTURE_DETECTION) {
SDL_Log("************ SD: Multigesture detection start **************");
}
gestureStart.time = mgesture.timestamp;
gestureStart.point.x = mgesture.x;
gestureStart.point.y = mgesture.y;
lastVector.reset();
gestureSwipe = false;
} else {
if (!gestureSwipe) {
vector sv; // Vector from start point to current position
float velocity;
float theta; // Angle between current vector and previous vector.
float duration;
sv.w = mgesture.x - gestureStart.point.x;
sv.h = mgesture.y - gestureStart.point.y;
float distance = sv.length();
if (lastVector.has_value()) {
// SDL2 timestamps were in milliseconds, SDL3 are nanoseconds. Given the
// normalized distances reported, using nanoseconds leads to 0 velocitySq.
duration = static_cast<float>(
(mgesture.timestamp - gestureStart.time) / 1000000.0);
velocity = distance / duration;
assert(!std::isinf(velocity));
theta = static_cast<float>(lastVector->getAngle(sv));
if (SWIPEDETECTOR_LOG_GESTURE_DETECTION) {
SDL_Log("SD: Detection: distance = %f, velocity = %f, theta = %f, sv angle = %f, sv angle normalized = %f, lastv angle = %f",
distance, velocity, theta,
sv.getAngle(),
sv.getAngleNormalized(),
lastVector->getAngle());
}
lastVector = sv;
// Multiple events with the same timestamp is a possibility
// hence the isinf() check.
if (std::abs(theta) < 3.0 && std::abs(mgesture.dDist) > 0.01 && !std::isinf(velocity) && velocity > 0.0007) {
if (SWIPEDETECTOR_LOG_GESTURE_DETECTION)
SDL_Log("----------------- SD: Swipe %s detected -----------------",
toString(sv.getDirection()).c_str());
gestureSwipe = true;
if (SDL_EventEnabled(SDL_EVENT_USER)) {
SDL_Event user_event;
// SDL will copy this entire struct! Initialize to keep memory
// checkers happy.
SDL_zero(user_event);
user_event.type = SDL_EVENT_USER;
user_event.user.code = swipeGesture;
user_event.user.data1 = SwipeDetector::directionToPointer(sv.getDirection());
user_event.user.data2 = NULL;
SDL_PushEvent(&user_event);
}
} else {
if (SWIPEDETECTOR_LOG_GESTURE_DETECTION) SDL_Log("SD: No swipe detected.");
result = true;
}
} else {
lastVector = sv;
result = true;
}
}
}
break;
}
default:
result = true;
}
return result;
}
+200
View File
@@ -0,0 +1,200 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2017-2020 Mark Callow, <khronos at callow dot im>.
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _SWIPE_DETECTOR_H
#define _SWIPE_DETECTOR_H
#if defined(_WIN32)
#define _USE_MATH_DEFINES
#endif
#include <optional>
#include <string>
#include <math.h>
#include <SDL3/SDL.h>
#include "SDL_gesture.h"
class SwipeDetector {
public:
enum class Direction { up, down, left, right };
SwipeDetector() : gestureSwipe(false) {}
bool doEvent(SDL_Event* event);
#if defined(_MSC_VER) && !defined(__clang__)
// Not clangcl
#pragma warning(push)
#pragma warning(disable : 4311)
#pragma warning(disable : 4302)
#pragma warning(disable : 4312)
#endif
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wvoid-pointer-to-int-cast"
#endif
// These conversions allow storing a Direction in a pointer.
// Ugly. Horrible. But preferable to allocating and freeing memory
// when passing the information in user events.
static inline Direction pointerToDirection(void* p) {
return static_cast<SwipeDetector::Direction>(reinterpret_cast<long>(p));
}
// Only preserves the low 32-bits of the pointer; perfect for this use.
static inline void* directionToPointer(SwipeDetector::Direction d) {
return reinterpret_cast<void*>(static_cast<long>(d));
}
#if defined(_MSC_VER) && !defined(__clang__)
#pragma warning(pop)
#endif
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
static const Uint32 swipeGesture = 0x01;
class vector {
public:
float w;
float h;
vector() : w(0.0), h(0.0) { }
vector(float _w, float _h) : w(_w), h(_h) { }
/**
* @~English
* @internal
* @brief Find the angle between the vector and the X-axis
*
* Positive angles increase counter-clockwise from the X-axis
* which has +x to the right.
*
* @return the angle between the vector and the x axis in degrees.
*/
double getAngle() {
double rad = atan2(h, w);
return rad * 180/M_PI;
}
/**
* @~English
* @internal
* @brief Find the angle between this vector and another.
*
* Positive angles increase counter-clockwise.
*
* @return the angle between the 2 vectors in degrees.
*/
double getAngle(const vector& v2) {
// Reputed to be more accurate but in our use so far both approaches give same answer.
//double rad = atan2f(v2.h, v2.w) - SDL_atan2(h, w);
double rad = atan2f(w * v2.h - h * v2.w, w * v2.w + h * v2.h);
return rad * 180/M_PI;
}
/**
* @~English
* @internal
* @brief Find the angle between the vector and the X-axis
*
* Positive angles increase counter-clockwise from the X-axis
* which has +x to the right. Value is normalized to the range 0 to 360.
*
* @return the angle between the vector and the x axis in degrees.
*/
double getAngleNormalized() {
double rad = atan2(h, w) + M_PI;
return fmod(rad*180/M_PI + 180, 360);
}
/**
* @~English
* @internal
* @brief Return the length of the vector.
*
* @return the length of the vector.
*/
float length() { return sqrt(w * w + h * h); }
/**
* @~English
* @internal
* @brief Return the direction of the vector.
*
* @return the direction
*/
Direction getDirection() {
double angle = getAngleNormalized();
return getDirection(angle);
}
/**
* @~English
* @internal
* @brief Return a direction given an angle.
*
* Directions are defined as follows:
*
* Up: [45, 135]
* Right: [0,45] and [315, 360]
* Down: [225, 315]
* Left: [135, 225]
*
* @param angle an angle from 0 to 360°
* @return the direction of an angle
*/
static Direction getDirection(double angle){
if (inRange(angle, 45, 135)) {
return Direction::down;
} else if (inRange(angle, 0, 45) || inRange(angle, 315, 360)) {
return Direction::right;
} else if (inRange(angle, 225, 315)) {
return Direction::up;
} else {
return Direction::left;
}
}
protected:
/**
* @~English
* @internal
* @brief Check if angle falls within an interval.
*
* @param angle an angle
* @param init the initial bound
* @param end the final bound
*
* @return true if the given angle is in the interval [init, end), false
* otherwise.
*/
static bool inRange(double angle, float init, float end){
return (angle >= init) && (angle < end);
}
};
protected:
struct gestureStart {
Uint64 time;
SDL_FPoint point;
gestureStart() { time = 0; point.x = point.y = 0.0; }
} gestureStart;
std::optional<vector> lastVector;
bool gestureSwipe;
};
[[nodiscard]] inline std::string toString(SwipeDetector::Direction dir) {
switch (dir) {
case SwipeDetector::Direction::up: return "up";
case SwipeDetector::Direction::down: return "down";
case SwipeDetector::Direction::left: return "left";
case SwipeDetector::Direction::right: return "right";
// This is to hide a warning from MSVC. According to the solution given
// in https://developercommunity.visualstudio.com/t/Visual-Studio-warning-on-Strongly-typed-/96302
// it is possible to construct an enum class with any value. Thus warning.
default: return "unknown";
}
}
#endif /* _SWIPE_DETECTOR_H */
@@ -0,0 +1,61 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2017-2020 Mark Callow, <khronos at callow dot im>.
* SPDX-License-Identifier: Apache-2.0
*/
#include <assert.h>
#include <ktx.h>
#include "TranscodeTargetStrToFmt.h"
ktx_transcode_fmt_e
TranscodeTargetStrToFmt(std::string format)
{
if (!format.compare("ETC1_RGB"))
return KTX_TTF_ETC1_RGB;
else if (!format.compare("ETC2_RGBA"))
return KTX_TTF_ETC2_RGBA;
else if (!format.compare("BC1_RGB"))
return KTX_TTF_BC1_RGB;
else if (!format.compare("BC3_RGBA"))
return KTX_TTF_BC3_RGBA;
else if (!format.compare("BC4_R"))
return KTX_TTF_BC4_R;
else if (!format.compare("BC5_RG"))
return KTX_TTF_BC5_RG;
else if (!format.compare("BC7_M6_RGB"))
return KTX_TTF_BC7_M6_RGB;
else if (!format.compare("BC7_M5_RGBA"))
return KTX_TTF_BC7_M5_RGBA;
else if (!format.compare("PVRTC1_4_RGB"))
return KTX_TTF_PVRTC1_4_RGB;
else if (!format.compare("PVRTC1_4_RGBA"))
return KTX_TTF_PVRTC1_4_RGBA;
else if (!format.compare("ASTC_4x4_RGBA"))
return KTX_TTF_ASTC_4x4_RGBA;
else if (!format.compare("PVRTC2_4_RGB"))
return KTX_TTF_PVRTC2_4_RGB;
else if (!format.compare("PVRTC2_4_RGBA"))
return KTX_TTF_PVRTC2_4_RGBA;
else if (!format.compare("ETC2_EAC_R11"))
return KTX_TTF_ETC2_EAC_R11;
else if (!format.compare("ETC2_EAC_RG11"))
return KTX_TTF_ETC2_EAC_RG11;
else if (!format.compare("RGBA32"))
return KTX_TTF_RGBA32;
else if (!format.compare("RGB565"))
return KTX_TTF_RGB565;
else if (!format.compare("BGR565"))
return KTX_TTF_BGR565;
else if (!format.compare("RGBA4444"))
return KTX_TTF_RGBA4444;
else if (!format.compare("ETC"))
return KTX_TTF_ETC;
else if (!format.compare("BC1_OR_3"))
return KTX_TTF_BC1_OR_3;
assert(false); // Error in args in sample table.
return static_cast<ktx_transcode_fmt_e>(-1); // To keep compilers happy.
}
@@ -0,0 +1,16 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2017-2020 Mark Callow, <khronos at callow dot im>.
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _TRANSCODE_TARGET_STR_TO_FMT_
#define _TRANSCODE_TARGET_STR_TO_FMT_
#include <string>
ktx_transcode_fmt_e TranscodeTargetStrToFmt(std::string format);
#endif /* _TRANSCODE_TARGET_STR_TO_FMT_ */
@@ -0,0 +1,21 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2021 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
#if !defined DISABLE_GLM_WARNINGS_H
// Temporarily disable the warnings caused by the GLM code.
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable: 4201)
#elif defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu-anonymous-struct"
#pragma clang diagnostic ignored "-Wnested-anon-types"
#endif
#endif /* DISABLE_GLM_WARNINGS_H */
+66
View File
@@ -0,0 +1,66 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2018-2020 Mark Callow, <khronos at callow dot im>.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief Custom exceptions for the load tests.
*/
#include <stdexcept>
#include <string>
#define OUT_OF_HOST_MEMORY -1
#define OUT_OF_DEVICE_MEMORY -2
#define FRAGMENTED_POOL -12
#define OUT_OF_POOL_MEMORY -1000069000
class bad_vulkan_alloc : public std::bad_alloc {
public:
bad_vulkan_alloc(int which, const char* _message) : std::bad_alloc() {
if (which == FRAGMENTED_POOL) {
message << "Pool fragmented when allocating for " << _message << ".";
} else {
std::string memtype;
switch (which) {
case OUT_OF_HOST_MEMORY: memtype = "host"; break;
case OUT_OF_DEVICE_MEMORY: memtype = "device"; break;
case OUT_OF_POOL_MEMORY: memtype = "pool"; break;
default: break;
}
message << "Out of " << memtype << " memory for " << _message << ".";
}
_what = message.str();
}
bad_vulkan_alloc(const bad_vulkan_alloc& in)
: std::bad_alloc()
, message{in.message.str()}
, _what{in._what}
{}
virtual const char* what() const throw() {
return _what.c_str();
}
protected:
std::stringstream message;
std::string _what;
};
class unsupported_ttype : public std::runtime_error {
public:
unsupported_ttype()
: std::runtime_error("Implementation does not support needed operations on image format") { }
unsupported_ttype(std::string& message) : std::runtime_error(message) { }
};
class unsupported_ctype : public std::runtime_error {
public:
unsupported_ctype()
: std::runtime_error("Unsupported compression format") { }
};
@@ -0,0 +1,63 @@
# Copyright 2020 The Khronos Group Inc.
# SPDX-License-Identifier: Apache-2.0
# Max2Obj Version 4.0 Mar 10th, 2001
#
# object default to come ...
#
v -5.000000 -5.000000 -5.000000
v 5.000000 -5.000000 -5.000000
v -5.000000 5.000000 -5.000000
v 5.000000 5.000000 -5.000000
v -5.000000 -5.000000 5.000000
v 5.000000 -5.000000 5.000000
v -5.000000 5.000000 5.000000
v 5.000000 5.000000 5.000000
# 8 vertices
vt 0.000000 0.000000 0.000000
vt 1.000000 0.000000 0.000000
vt 0.000000 1.000000 0.000000
vt 1.000000 1.000000 0.000000
vt 0.000000 0.000000 0.000000
vt 1.000000 0.000000 0.000000
vt 0.000000 1.000000 0.000000
vt 1.000000 1.000000 0.000000
vt 0.000000 0.000000 0.000000
vt 1.000000 0.000000 0.000000
vt 0.000000 1.000000 0.000000
vt 1.000000 1.000000 0.000000
# 12 texture vertices
vn 0.000000 0.000000 -2.000000
vn 0.000000 0.000000 -1.000000
vn 0.000000 0.000000 -1.000000
vn 0.000000 0.000000 -2.000000
vn 0.000000 -0.000000 2.000000
vn 0.000000 -0.000000 1.000000
vn 0.000000 -0.000000 1.000000
vn 0.000000 -0.000000 2.000000
# 8 vertex normals
g default
s 2
f 1/10/1 3/12/3 4/11/4
f 4/11/4 2/9/2 1/10/1
s 4
f 5/9/5 6/10/6 8/12/8
f 8/12/8 7/11/7 5/9/5
s 8
f 1/5/1 2/6/2 6/8/6
f 6/8/6 5/7/5 1/5/1
s 16
f 2/1/2 4/2/4 8/4/8
f 8/4/8 6/3/6 2/1/2
s 32
f 4/5/4 3/6/3 7/8/7
f 7/8/7 8/7/8 4/5/4
s 64
f 3/1/3 1/2/1 5/4/5
f 5/4/5 7/3/7 3/1/3
# 12 faces
g
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,18 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2021 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
#if !defined REENABLE_WARNINGS_H
// Reenable warnings disabled by, e.g. disable_glm_warnings.h.
#if defined(_MSC_VER)
#pragma warning(pop)
#elif defined(__clang__)
#pragma clang diagnostic pop
#endif
#endif /* REENABLE_WARNINGS_H */
+448
View File
@@ -0,0 +1,448 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2016-2020 Mark Callow
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef VECMATH_9B7E1CFE346D11E6AFA2D7DC87495A69_H
#define VECMATH_9B7E1CFE346D11E6AFA2D7DC87495A69_H
/**
* @file
* @~English
*
* @brief Vector math package modelled after GLSL.
*/
#include <assert.h>
#include <math.h>
struct vec2 {
// Anonymous unions are portable.
union {
float x;
float r;
};
union {
float y;
float g;
};
vec2() { };
};
struct vec3 {
union {
float x;
float r;
};
union {
float y;
float g;
};
union {
float z;
float b;
};
vec3() { }
vec3(float x, float y, float z) : x(x), y(y), z(z) { }
vec3(const vec3& value)
: x(value.x), y(value.y), z(value.z) { }
vec3 operator-() const
{
return vec3(-x, -y, -z);
}
vec3 operator-(const vec3& value) const
{
return vec3(this->x - value.x, this->y - value.y, this->z - value.z);
}
vec3 operator/(float divisor) const
{
return vec3(x / divisor, y / divisor, z / divisor);
}
vec3& operator/=(float divisor)
{
x /= divisor;
y /= divisor;
z /= divisor;
return *this;
}
vec3& operator=(const vec3& value)
{
x = value.x;
y = value.y;
z = value.z;
return *this;
}
float operator[](int i) const
{
switch (i) {
case 0: return x;
case 1: return y;
case 2: return z;
default:
assert(false);
return z;
}
}
float& operator[](int i)
{
switch (i) {
case 0: return x;
case 1: return y;
case 2: return z;
default:
assert(false);
return z;
}
}
vec3 cross(const vec3& value) const
{
return vec3::cross(*this, value);
}
float dot(const vec3& vec) const
{
return vec3::dot(*this, vec);
}
float length() const
{
return sqrt(dot(*this));
}
vec3& normalize()
{
float length = this->length();
return (length > 0.0f ? *this /= length : *this);
}
static vec3 cross(const vec3& a, const vec3& b)
{
return vec3(a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x);
}
static float dot(const vec3& a, const vec3& b)
{
return a.x * b.x + a.y * b.y + a.z * b.z;
}
static vec3 normalize (const vec3& input)
{
float length = input.length();
return (length > 0.0f ? input / length : input);
}
};
struct vec4 {
union {
float x;
float r;
};
union {
float y;
float g;
};
union {
float z;
float b;
};
union {
float w;
float a;
};
vec4() { }
vec4(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) { }
vec4(const vec3& value, float w)
: x(value.x), y(value.y), z(value.z), w(w) { }
vec4(const vec4& value)
: x(value.x), y(value.y), z(value.z), w(value.w) { }
vec4 operator/(float divisor) const
{
return vec4(x / divisor, y / divisor, z / divisor, w / divisor);
}
vec4& operator/=(float divisor)
{
x /= divisor;
y /= divisor;
z /= divisor;
w /= divisor;
return *this;
}
vec4 operator*(float multiplicand) const
{
return vec4(x * multiplicand, y * multiplicand, z * multiplicand,
w * multiplicand);
}
vec4& operator=(const vec4& value)
{
x = value.x;
y = value.y;
z = value.z;
w = value.w;
return *this;
}
float operator[](int i) const
{
switch (i) {
case 0: return x;
case 1: return y;
case 2: return z;
case 3: return w;
default:
assert(false);
return z;
}
}
float& operator[](int i)
{
switch (i) {
case 0: return x;
case 1: return y;
case 2: return z;
case 3: return w;
default:
assert(false);
return z;
}
}
float dot(const vec4& vec) const
{
return vec4::dot(*this, vec);
}
float length() const
{
return sqrt(dot(*this));
}
static float dot(const vec4& a, const vec4& b)
{
return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
}
static vec4 normalize (const vec4& input)
{
float length = input.length();
return (length > 0.0f ? input / length : input);
}
};
struct mat3 {
vec3 m[3];
mat3()
{
m[0] = vec3(1.0f, 0.0f, 0.0f);
m[1] = vec3(0.0f, 1.0f, 0.0f);
m[2] = vec3(0.0f, 0.0f, 1.0f);
}
mat3(const vec3& value1, const vec3& value2, const vec3& value3)
{
m[0] = value1;
m[1] = value2;
m[2] = value3;
}
mat3 transpose() const
{
return mat3(vec3(m[0].x, m[1].x, m[2].x),
vec3(m[0].y, m[1].y, m[2].y),
vec3(m[0].z, m[1].z, m[2].z));
}
};
struct mat4 {
vec4 m[4];
mat4()
{
m[0] = vec4(1.0f, 0.0f, 0.0f, 0.0f);
m[1] = vec4(0.0f, 1.0f, 0.0f, 0.0f);
m[2] = vec4(0.0f, 0.0f, 1.0f, 0.0f);
m[3] = vec4(0.0f, 0.0f, 0.0f, 1.0f);
}
mat4(const vec4& value1, const vec4& value2,
const vec4& value3, const vec4& value4)
{
m[0] = value1;
m[1] = value2;
m[2] = value3;
m[3] = value4;
}
mat4 operator*(float value) const
{
return mat4(m[0] * value, m[1] * value, m[2] * value, m[3] * value);
}
mat4 operator*(const mat4& value) const
{
mat4 right = value.transpose();
return mat4(
vec4(m[0].dot(right.m[0]), m[0].dot(right.m[1]),
m[0].dot(right.m[2]), m[0].dot(right.m[3])),
vec4(m[1].dot(right.m[0]), m[1].dot(right.m[1]),
m[1].dot(right.m[2]), m[1].dot(right.m[3])),
vec4(m[2].dot(right.m[0]), m[2].dot(right.m[1]),
m[2].dot(right.m[2]), m[2].dot(right.m[3])),
vec4(m[3].dot(right.m[0]), m[3].dot(right.m[1]),
m[3].dot(right.m[2]), m[3].dot(right.m[3]))
);
}
vec4 operator*(const vec4& value) const
{
return vec4(m[0].dot(value), m[1].dot(value),
m[2].dot(value), m[3].dot(value));
}
vec4& operator[](int i)
{
return m[i];
}
mat4 transpose() const
{
return mat4(vec4(m[0][0], m[1][0], m[2][0], m[3][0]),
vec4(m[0][1], m[1][1], m[2][1], m[3][1]),
vec4(m[0][2], m[1][2], m[2][2], m[3][2]),
vec4(m[0][3], m[1][3], m[2][3], m[3][3]));
}
static mat4 translate(const vec3& trans)
{
return mat4(vec4(1.0f, 0.0f, 0.0f, trans.x),
vec4(0.0f, 1.0f, 0.0f, trans.y),
vec4(0.0f, 0.0f, 1.0f, trans.z),
vec4(0.0f, 0.0f, 0.0f, 1.0f));
}
static mat4 translate(float x, float y, float z)
{
return mat4(vec4(1.0f, 0.0f, 0.0f, x),
vec4(0.0f, 1.0f, 0.0f, y),
vec4(0.0f, 0.0f, 1.0f, z),
vec4(0.0f, 0.0f, 0.0f, 1.0f));
}
static mat4 scale(const vec3& scale)
{
return mat4(vec4(scale.x, 0.0f, 0.0f, 0.0f),
vec4(0.0f, scale.y, 0.0f, 0.0f),
vec4(0.0f, 0.0f, scale.z, 0.0f),
vec4(0.0f, 0.0f, 0.0f, 1.0f));
}
static mat4 scale(float x, float y, float z)
{
return mat4(vec4(x, 0.0f, 0.0f, 0.0f),
vec4(0.0f, y, 0.0f, 0.0f),
vec4(0.0f, 0.0f, z, 0.0f),
vec4(0.0f, 0.0f, 0.0f, 1.0f));
}
static mat4 frustum(float left, float right, float bottom, float top,
float zNear, float zFar)
{
return mat4(
vec4(2.0f * zNear / (right - left), 0.0f,
(right + left) / (right - left), 0.0f),
vec4(0.0f, 2.0f * zNear / (top - bottom),
(top + bottom) / (top - bottom), 0.0f),
vec4(0.0f, 0.0f, (zFar + zNear) / (zNear - zFar),
2.0f * zFar * zNear / (zNear - zFar)),
vec4(0.0f, 0.0f, -1.0f, 0.0f)
);
}
static mat4 ortho(float left, float right, float bottom, float top,
float zNear, float zFar)
{
return mat4(
vec4(2.0f / (right - left), 0.0f, 0.0f,
(right + left) / (left - right)),
vec4(0.0f, 2.0f / (top - bottom), 0.0f,
(top + bottom) / (bottom - top)),
vec4(0.0f, 0.0f, 2.0f / (zNear - zFar),
(zFar + zNear) / (zNear - zFar)),
vec4(0.0f, 0.0f, 0.0f, 1.0f)
);
}
static mat4 lookAt(const vec3& eye, const vec3& center, const vec3& up)
{
vec3 const forward(vec3::normalize(center - eye));
vec3 const side(vec3::normalize(vec3::cross(forward, up)));
vec3 const u(vec3::cross(side, forward));
mat4 result;
result[0][0] = side.x;
result[1][0] = side.y;
result[2][0] = side.z;
result[0][1] = u.x;
result[1][1] = u.y;
result[2][1] = u.z;
result[0][2] =-forward.x;
result[1][2] =-forward.y;
result[2][2] =-forward.z;
result[3][0] =-vec3::dot(side, eye);
result[3][1] =-vec3::dot(u, eye);
result[3][2] = vec3::dot(forward, eye);
return result;
}
static mat4 lookAt(float eyeX, float eyeY, float eyeZ,
float centerX, float centerY, float centerZ,
float upX, float upY, float upZ)
{
return mat4::lookAt(vec3(eyeX, eyeY, eyeZ),
vec3(centerX, centerY, centerZ),
vec3(upX, upY, upZ));
}
static mat4 perspective(float fovY, float aspect, float zNear, float zFar)
{
float scaleY = 1.0f / tan(fovY * 3.1415962f / 360.0f);
return mat4(
vec4(scaleY / aspect, 0.0f, 0.0f, 0.0f),
vec4(0.0f, scaleY, 0.0f, 0.0f),
vec4(0.0f, 0.0f, (zFar + zNear) / (zNear - zFar),
(2.0f * zFar * zNear) / (zNear - zFar)),
vec4(0.0f, 0.0f, -1.0f, 0.0f)
);
}
};
#endif /* VECMATH_9B7E1CFE-346D-11E6-AFA2-D7DC87495A69_H */
+80
View File
@@ -0,0 +1,80 @@
# Copyright 2020 Andreas Atteneder
# SPDX-License-Identifier: Apache-2.0
function(compile_shader shader_target shader_name shader_src_path shader_path)
set(vert_name "${shader_name}.vert")
set(vert2spirv_in "${shader_src_path}/${vert_name}")
set(vert2spirv_out "${CMAKE_CURRENT_BINARY_DIR}/${shader_path}/${vert_name}.spv")
add_custom_command(OUTPUT
${vert2spirv_out}
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/${shader_path}
COMMAND glslc "-fshader-stage=vertex" -o "${vert2spirv_out}" "${vert2spirv_in}"
DEPENDS ${vert2spirv_in}
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
COMMENT "Compiling ${vert_name}."
VERBATIM
)
set(frag_name "${shader_name}.frag")
set(frag2spirv_in "${shader_src_path}/${frag_name}")
set(frag2spirv_out "${CMAKE_CURRENT_BINARY_DIR}/${shader_path}/${frag_name}.spv")
add_custom_command(OUTPUT
${frag2spirv_out}
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/${shader_path}
COMMAND glslc "-fshader-stage=fragment" -o "${frag2spirv_out}" "${frag2spirv_in}"
DEPENDS ${frag2spirv_in}
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
COMMENT "Compiling ${frag_name}."
VERBATIM
)
add_custom_target(
${shader_target}
DEPENDS
${vert2spirv_out}
${frag2spirv_out}
SOURCES
${vert2spirv_in}
${frag2spirv_in}
)
set_target_properties(${shader_target} PROPERTIES EXCLUDE_FROM_ALL "FALSE")
set(SHADER_SOURCES ${SHADER_SOURCES} ${frag2spirv_out} ${vert2spirv_out} PARENT_SCOPE)
endfunction(compile_shader)
function(compile_shader_list shader_target shader_src_path shader_path)
foreach(shader ${ARGN})
set(spirv_in "${shader_src_path}/${shader}")
set(spirv_out "${CMAKE_CURRENT_BINARY_DIR}/${shader_path}/${shader}.spv")
add_custom_command(OUTPUT
${spirv_out}
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/${shader_path}
COMMAND glslc -o "${spirv_out}" "${spirv_in}"
DEPENDS ${spirv_in}
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
COMMENT "Compiling ${shader}."
VERBATIM
)
list(APPEND inputs ${spirv_in})
list(APPEND outputs ${spirv_out})
endforeach()
add_custom_target(
${shader_target}
DEPENDS ${outputs}
SOURCES ${inputs}
)
set_target_properties(${shader_target} PROPERTIES EXCLUDE_FROM_ALL "FALSE")
set(SHADER_SOURCES ${SHADER_SOURCES} ${outputs} PARENT_SCOPE)
endfunction()
+72
View File
@@ -0,0 +1,72 @@
/*
* Copyright 2016-2020 Mark Callow
* SPDX-License-Identifier: Apache-2.0
*/
static const float cube_face[] =
{
-1.0f, +1.0f, +1.0f, /* Front */
+1.0f, -1.0f, +1.0f,
+1.0f, +1.0f, +1.0f,
-1.0f, -1.0f, +1.0f,
-1.0f, +1.0f, -1.0f, /* Back */
+1.0f, -1.0f, -1.0f,
+1.0f, +1.0f, -1.0f,
-1.0f, -1.0f, -1.0f,
+1.0f, -1.0f, +1.0f, /* Right */
+1.0f, +1.0f, -1.0f,
+1.0f, +1.0f, +1.0f,
+1.0f, -1.0f, -1.0f,
-1.0f, -1.0f, +1.0f, /* Left */
-1.0f, +1.0f, -1.0f,
-1.0f, +1.0f, +1.0f,
-1.0f, -1.0f, -1.0f,
-1.0f, -1.0f, +1.0f, /* Bottom */
+1.0f, -1.0f, -1.0f,
+1.0f, -1.0f, +1.0f,
-1.0f, -1.0f, -1.0f,
-1.0f, +1.0f, +1.0f, /* Top */
+1.0f, +1.0f, -1.0f,
+1.0f, +1.0f, +1.0f,
-1.0f, +1.0f, -1.0f,
};
#define CUBE_NUM_FACE_COMPONENTS 3
#define CUBE_FACE_STRIDE (sizeof(float) * CUBE_NUM_FACE_COMPONENTS)
static const float cube_color[] = /* almost random colors */
{
0.7f, 0.1f, 0.2f, 0.0f, 0.8f, 0.9f, 0.3f, 0.0f, 0.4f, 1.0f, 0.5f, 0.0f, 0.0f, 0.6f, 0.1f, 0.0f,
0.8f, 0.2f, 0.3f, 0.0f, 0.9f, 1.0f, 0.4f, 0.0f, 0.5f, 0.0f, 0.6f, 0.0f, 0.1f, 0.7f, 0.2f, 0.0f,
0.9f, 0.3f, 0.4f, 0.0f, 1.0f, 0.0f, 0.5f, 0.0f, 0.6f, 0.1f, 0.7f, 0.0f, 0.2f, 0.8f, 0.3f, 0.0f,
1.0f, 0.4f, 0.5f, 0.0f, 0.0f, 0.1f, 0.6f, 0.0f, 0.7f, 0.2f, 0.8f, 0.0f, 0.3f, 0.9f, 0.4f, 0.0f,
0.0f, 0.5f, 0.6f, 0.0f, 0.1f, 0.2f, 0.7f, 0.0f, 0.8f, 0.3f, 0.9f, 0.0f, 0.4f, 1.0f, 0.5f, 0.0f,
0.1f, 0.6f, 0.7f, 0.0f, 0.2f, 0.3f, 0.8f, 0.0f, 0.9f, 0.4f, 1.0f, 0.0f, 0.5f, 0.0f, 0.6f, 0.0f,
};
static const float cube_texture[] =
{
0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f,
1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f,
1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f,
1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f
};
static const float cube_normal[] =
{
0.0f, 0.0f, +1.0f, 0.0f, 0.0f, +1.0f, 0.0f, 0.0f, +1.0f, 0.0f, 0.0f, +1.0f,
0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f,
+1.0f, 0.0f, 0.0f, +1.0f, 0.0f, 0.0f, +1.0f, 0.0f, 0.0f, +1.0f, 0.0f, 0.0f,
-1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f,
0.0f, +1.0f, 0.0f, 0.0f, +1.0f, 0.0f, 0.0f, +1.0f, 0.0f, 0.0f, +1.0f, 0.0f,
};
static const unsigned short cube_index_buffer[] = {
0, 3, 1, 2, 0, 1, /* Front */
6, 5, 4, 5, 7, 4, /* Back */
8,11, 9,10, 8, 9, /* Right */
15,12,13,12,14,13, /* Left */
16,19,17,18,16,17, /* Bottom */
23,20,21,20,22,21 /* Top */
};
#define CUBE_NUM_INDICES (sizeof(cube_index_buffer) / sizeof(unsigned short))
+155
View File
@@ -0,0 +1,155 @@
/*
* Vulkan Samples
*
* Copyright 2015-2016 Valve Corporation
* Copyright 2015-2016 LunarG, Inc.
* SPDX-License-Identifier: Apache-2.0
*/
//--------------------------------------------------------------------------------------
// Mesh and VertexFormat Data
//--------------------------------------------------------------------------------------
struct Vertex {
float posX, posY, posZ, posW; // Position data
float r, g, b, a; // Color
};
struct VertexUV {
float posX, posY, posZ, posW; // Position data
float u, v; // texture u,v
};
#define XYZ1(_x_, _y_, _z_) (_x_), (_y_), (_z_), 1.f
#define UV(_u_, _v_) (_u_), (_v_)
static const Vertex g_vbData[] = {
{XYZ1(-1, -1, -1), XYZ1(0.f, 0.f, 0.f)},
{XYZ1(1, -1, -1), XYZ1(1.f, 0.f, 0.f)},
{XYZ1(-1, 1, -1), XYZ1(0.f, 1.f, 0.f)},
{XYZ1(-1, 1, -1), XYZ1(0.f, 1.f, 0.f)},
{XYZ1(1, -1, -1), XYZ1(1.f, 0.f, 0.f)},
{XYZ1(1, 1, -1), XYZ1(1.f, 1.f, 0.f)},
{XYZ1(-1, -1, 1), XYZ1(0.f, 0.f, 1.f)},
{XYZ1(-1, 1, 1), XYZ1(0.f, 1.f, 1.f)},
{XYZ1(1, -1, 1), XYZ1(1.f, 0.f, 1.f)},
{XYZ1(1, -1, 1), XYZ1(1.f, 0.f, 1.f)},
{XYZ1(-1, 1, 1), XYZ1(0.f, 1.f, 1.f)},
{XYZ1(1, 1, 1), XYZ1(1.f, 1.f, 1.f)},
{XYZ1(1, 1, 1), XYZ1(1.f, 1.f, 1.f)},
{XYZ1(1, 1, -1), XYZ1(1.f, 1.f, 0.f)},
{XYZ1(1, -1, 1), XYZ1(1.f, 0.f, 1.f)},
{XYZ1(1, -1, 1), XYZ1(1.f, 0.f, 1.f)},
{XYZ1(1, 1, -1), XYZ1(1.f, 1.f, 0.f)},
{XYZ1(1, -1, -1), XYZ1(1.f, 0.f, 0.f)},
{XYZ1(-1, 1, 1), XYZ1(0.f, 1.f, 1.f)},
{XYZ1(-1, -1, 1), XYZ1(0.f, 0.f, 1.f)},
{XYZ1(-1, 1, -1), XYZ1(0.f, 1.f, 0.f)},
{XYZ1(-1, 1, -1), XYZ1(0.f, 1.f, 0.f)},
{XYZ1(-1, -1, 1), XYZ1(0.f, 0.f, 1.f)},
{XYZ1(-1, -1, -1), XYZ1(0.f, 0.f, 0.f)},
{XYZ1(1, 1, 1), XYZ1(1.f, 1.f, 1.f)},
{XYZ1(-1, 1, 1), XYZ1(0.f, 1.f, 1.f)},
{XYZ1(1, 1, -1), XYZ1(1.f, 1.f, 0.f)},
{XYZ1(1, 1, -1), XYZ1(1.f, 1.f, 0.f)},
{XYZ1(-1, 1, 1), XYZ1(0.f, 1.f, 1.f)},
{XYZ1(-1, 1, -1), XYZ1(0.f, 1.f, 0.f)},
{XYZ1(1, -1, 1), XYZ1(1.f, 0.f, 1.f)},
{XYZ1(1, -1, -1), XYZ1(1.f, 0.f, 0.f)},
{XYZ1(-1, -1, 1), XYZ1(0.f, 0.f, 1.f)},
{XYZ1(-1, -1, 1), XYZ1(0.f, 0.f, 1.f)},
{XYZ1(1, -1, -1), XYZ1(1.f, 0.f, 0.f)},
{XYZ1(-1, -1, -1), XYZ1(0.f, 0.f, 0.f)},
};
static const Vertex g_vb_solid_face_colors_Data[] = {
{XYZ1(-1, -1, -1), XYZ1(1.f, 0.f, 0.f)},
{XYZ1(1, -1, -1), XYZ1(1.f, 0.f, 0.f)},
{XYZ1(-1, 1, -1), XYZ1(1.f, 0.f, 0.f)},
{XYZ1(-1, 1, -1), XYZ1(1.f, 0.f, 0.f)},
{XYZ1(1, -1, -1), XYZ1(1.f, 0.f, 0.f)},
{XYZ1(1, 1, -1), XYZ1(1.f, 0.f, 0.f)},
{XYZ1(-1, -1, 1), XYZ1(0.f, 1.f, 0.f)},
{XYZ1(-1, 1, 1), XYZ1(0.f, 1.f, 0.f)},
{XYZ1(1, -1, 1), XYZ1(0.f, 1.f, 0.f)},
{XYZ1(1, -1, 1), XYZ1(0.f, 1.f, 0.f)},
{XYZ1(-1, 1, 1), XYZ1(0.f, 1.f, 0.f)},
{XYZ1(1, 1, 1), XYZ1(0.f, 1.f, 0.f)},
{XYZ1(1, 1, 1), XYZ1(0.f, 0.f, 1.f)},
{XYZ1(1, 1, -1), XYZ1(0.f, 0.f, 1.f)},
{XYZ1(1, -1, 1), XYZ1(0.f, 0.f, 1.f)},
{XYZ1(1, -1, 1), XYZ1(0.f, 0.f, 1.f)},
{XYZ1(1, 1, -1), XYZ1(0.f, 0.f, 1.f)},
{XYZ1(1, -1, -1), XYZ1(0.f, 0.f, 1.f)},
{XYZ1(-1, 1, 1), XYZ1(1.f, 1.f, 0.f)},
{XYZ1(-1, -1, 1), XYZ1(1.f, 1.f, 0.f)},
{XYZ1(-1, 1, -1), XYZ1(1.f, 1.f, 0.f)},
{XYZ1(-1, 1, -1), XYZ1(1.f, 1.f, 0.f)},
{XYZ1(-1, -1, 1), XYZ1(1.f, 1.f, 0.f)},
{XYZ1(-1, -1, -1), XYZ1(1.f, 1.f, 0.f)},
{XYZ1(1, 1, 1), XYZ1(1.f, 0.f, 1.f)},
{XYZ1(-1, 1, 1), XYZ1(1.f, 0.f, 1.f)},
{XYZ1(1, 1, -1), XYZ1(1.f, 0.f, 1.f)},
{XYZ1(1, 1, -1), XYZ1(1.f, 0.f, 1.f)},
{XYZ1(-1, 1, 1), XYZ1(1.f, 0.f, 1.f)},
{XYZ1(-1, 1, -1), XYZ1(1.f, 0.f, 1.f)},
{XYZ1(1, -1, 1), XYZ1(0.f, 1.f, 1.f)},
{XYZ1(1, -1, -1), XYZ1(0.f, 1.f, 1.f)},
{XYZ1(-1, -1, 1), XYZ1(0.f, 1.f, 1.f)},
{XYZ1(-1, -1, 1), XYZ1(0.f, 1.f, 1.f)},
{XYZ1(1, -1, -1), XYZ1(0.f, 1.f, 1.f)},
{XYZ1(-1, -1, -1), XYZ1(0.f, 1.f, 1.f)},
};
static const VertexUV g_vb_texture_Data[] = {
{XYZ1(-1, -1, -1), UV(0.f, 0.f)},
{XYZ1(-1, 1, 1), UV(1.f, 1.f)},
{XYZ1(-1, -1, 1), UV(1.f, 0.f)},
{XYZ1(-1, 1, 1), UV(1.f, 1.f)},
{XYZ1(-1, -1, -1), UV(0.f, 0.f)},
{XYZ1(-1, 1, -1), UV(0.f, 1.f)},
{XYZ1(-1, -1, -1), UV(1.f, 0.f)},
{XYZ1(1, -1, -1), UV(0.f, 0.f)},
{XYZ1(1, 1, -1), UV(0.f, 1.f)},
{XYZ1(-1, -1, -1), UV(1.f, 0.f)},
{XYZ1(1, 1, -1), UV(0.f, 1.f)},
{XYZ1(-1, 1, -1), UV(1.f, 1.f)},
{XYZ1(-1, -1, -1), UV(1.f, 1.f)},
{XYZ1(1, -1, 1), UV(0.f, 0.f)},
{XYZ1(1, -1, -1), UV(1.f, 0.f)},
{XYZ1(-1, -1, -1), UV(1.f, 1.f)},
{XYZ1(-1, -1, 1), UV(0.f, 1.f)},
{XYZ1(1, -1, 1), UV(0.f, 0.f)},
{XYZ1(-1, 1, -1), UV(1.f, 1.f)},
{XYZ1(1, 1, 1), UV(0.f, 0.f)},
{XYZ1(-1, 1, 1), UV(0.f, 1.f)},
{XYZ1(-1, 1, -1), UV(1.f, 1.f)},
{XYZ1(1, 1, -1), UV(1.f, 0.f)},
{XYZ1(1, 1, 1), UV(0.f, 0.f)},
{XYZ1(1, 1, -1), UV(1.f, 1.f)},
{XYZ1(1, -1, 1), UV(0.f, 0.f)},
{XYZ1(1, 1, 1), UV(0.f, 1.f)},
{XYZ1(1, -1, 1), UV(0.f, 0.f)},
{XYZ1(1, 1, -1), UV(1.f, 1.f)},
{XYZ1(1, -1, -1), UV(1.f, 0.f)},
{XYZ1(-1, 1, 1), UV(0.f, 1.f)},
{XYZ1(1, 1, 1), UV(1.f, 1.f)},
{XYZ1(-1, -1, 1), UV(0.f, 0.f)},
{XYZ1(-1, -1, 1), UV(0.f, 0.f)},
{XYZ1(1, 1, 1), UV(1.f, 1.f)},
{XYZ1(1, -1, 1), UV(1.f, 0.f)},
};
+23
View File
@@ -0,0 +1,23 @@
/*
* Copyright 2016-2020 Mark Callow
* SPDX-License-Identifier: Apache-2.0
*/
/*
* Frame definition
*/
static const GLbyte frame_position[] =
{
-1, -1, 0,
1, -1, 0,
1, 1, 0,
-1, 1, 0
};
static const GLbyte frame_color[] =
{
1, 1, 0,
1, 1, 0,
1, 1, 0,
1, 1, 0
};
+34
View File
@@ -0,0 +1,34 @@
/* -*- tab-width: 4; -*- */
/* vi: set et sw=2 ts=4 expandtab: */
/*
* Copyright 2016-2020 Mark Callow
* SPDX-License-Identifier: Apache-2.0
*/
/*
* Quad definition
*/
static const GLfloat quad_position[] =
{
1.0f, -1.0f, 0,
1.0f, 1.0f, 0,
-1.0f, -1.0f, 0,
-1.0f, 1.0f, 0
};
static const GLfloat quad_color[] =
{
0.7f, 0.1f, 0.2f,
0.8f, 0.9f, 0.3f,
0.4f, 1.0f, 0.5f,
0.0f, 0.6f, 0.1f
};
static GLfloat quad_texture[] =
{
1.0f, 0.0f,
1.0f, 1.0f,
0.0f, 0.0f,
0.0f, 1.0f
};
+449
View File
@@ -0,0 +1,449 @@
# Copyright 2020 Andreas Atteneder
# SPDX-License-Identifier: Apache-2.0
set(OPENGL_ES_EMULATOR "" CACHE PATH "Path to OpenGL ES emulation libraries")
if(NOT APPLE_LOCKED_OS)
find_package(OpenGL REQUIRED)
endif()
if(WIN32)
find_package(GLEW REQUIRED)
endif()
function( create_gl_target target version sources common_resources test_images
KTX_GL_CONTEXT_PROFILE
KTX_GL_CONTEXT_MAJOR_VERSION KTX_GL_CONTEXT_MINOR_VERSION
EMULATE_GLES)
set( resources ${common_resources};${test_images} )
add_executable( ${target}
${EXE_FLAG}
glloadtests.cmake
${sources}
${resources}
)
set_code_sign(${target})
# Nota Bene.
#
# 1. With the Visual Studio generator, at least, The SDL and GLEW
# includes coming from GLAppSDL are being converted to system
# includes. To see them in VS, view the whole command line in
# the compile section of the project properties and look at the
# Additional Options pane at the bottom.
# 2. GL_APP_SDL's INTERFACE_INCLUDE_DIRECTORIES includes the SYSTEM
# include from appfwSDL.
#
# I do not understand the reasons for either of these.
target_include_directories(
${target}
PRIVATE
$<TARGET_PROPERTY:GLAppSDL,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:ktx,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:objUtil,INTERFACE_INCLUDE_DIRECTORIES>
)
set_target_properties(${target} PROPERTIES
CXX_VISIBILITY_PRESET ${STATIC_APP_LIB_SYMBOL_VISIBILITY}
)
target_link_libraries(
${target}
objUtil
GLAppSDL
appfwSDL
ktx
)
if(NOT EMSCRIPTEN AND NOT EMULATE_GLES)
target_link_libraries(
${target}
${OPENGL_LIBRARIES}
)
endif()
if(APPLE)
if(IOS)
# This is location where CMake puts the configured Info.plist.
# I have not found a CMake variable for this.
set( launch_screen ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${target}.dir/LaunchScreen.storyboard )
configure_file( glloadtests/resources/ios/LaunchScreen.storyboard.in
${launch_screen}
)
set( INFO_PLIST_IN "${PROJECT_SOURCE_DIR}/tests/loadtests/glloadtests/resources/ios/Info.plist.in" )
set( icon_launch_assets
${PROJECT_SOURCE_DIR}/icons/ios/CommonIcons.xcassets
glloadtests/resources/ios/LaunchImages.xcassets
${launch_screen}
)
target_sources( ${target}
PRIVATE
${PROJECT_SOURCE_DIR}/icons/ios/CommonIcons.xcassets
glloadtests/resources/ios/LaunchImages.xcassets
glloadtests/resources/ios/LaunchScreen.storyboard.in
)
# Add to resources so they'll be copied to the bundle.
list( APPEND resources ${icon_launch_assets} )
target_link_libraries(
${target}
${AudioToolbox_LIBRARY}
${AVFoundation_LIBRARY}
${CoreAudio_LIBRARY}
${CoreBluetooth_LIBRARY}
${CoreGraphics_LIBRARY}
${CoreMotion_LIBRARY}
${CoreHaptics_LIBRARY}
${Foundation_LIBRARY}
${GameController_LIBRARY}
${Metal_LIBRARY}
${OpenGLES_LIBRARY}
${QuartzCore_LIBRARY}
${UIKit_LIBRARY}
)
else()
set( INFO_PLIST_IN "${PROJECT_SOURCE_DIR}/tests/loadtests/glloadtests/resources/mac/Info.plist.in" )
endif()
elseif(EMSCRIPTEN)
# Beware of de-duplication in list expansion for commands and options.
# SHELL: prevents it but if they are separate items in the list they
# be de-duplicated.
list( TRANSFORM test_images REPLACE
"(${PROJECT_SOURCE_DIR}/tests/testimages/([a-zA-Z0-9_].*$))"
"SHELL:--preload-file \\1@\\2"
OUTPUT_VARIABLE preloads
)
target_link_options(
${target}
PRIVATE
"SHELL:--source-map-base ./"
${preloads}
"SHELL:--exclude-file '${PROJECT_SOURCE_DIR}/tests/testimages/cubemap*'"
"SHELL:--use-port=sdl3"
"SHELL:-s ALLOW_MEMORY_GROWTH=1"
"SHELL:-s DISABLE_EXCEPTION_CATCHING=0"
"SHELL:-s USE_WEBGL2=1"
)
elseif(WIN32)
target_sources(
${target}
PRIVATE
glloadtests/resources/win/glloadtests.rc
glloadtests/resources/win/resource.h
)
if(EMULATE_GLES)
if (KTX_GL_CONTEXT_MAJOR_VERSION EQUAL 1)
target_link_libraries(
${target}
"${OPENGL_ES_EMULATOR}/libGLES_CM.lib"
"${OPENGL_ES_EMULATOR}/libEGL.lib"
)
else()
target_link_libraries(
${target}
"${OPENGL_ES_EMULATOR}/libGLESv2.lib"
"${OPENGL_ES_EMULATOR}/libEGL.lib"
)
endif()
else()
target_link_libraries(
${target}
${GLEW_LIBRARIES}
)
endif()
ensure_runtime_dependencies_windows(${target})
elseif(LINUX)
# The output file is configured at CMake config time.
configure_file(glloadtests/resources/linux/glloadtests.desktop.in
${CMAKE_CURRENT_BINARY_DIR}/${target}.desktop
)
target_sources(
${target}
PRIVATE
# Put the input file in sources as that is what must be edited.
glloadtests/resources/linux/glloadtests.desktop.in
#${CMAKE_CURRENT_BINARY_DIR}/${target}.desktop
)
endif()
target_link_libraries( ${target} ${LOAD_TEST_COMMON_LIBS} )
if(NOT EMULATE_GLES OR KTX_GL_CONTEXT_MAJOR_VERSION GREATER 1)
target_compile_definitions(
${target}
PRIVATE
$<TARGET_PROPERTY:ktx,INTERFACE_COMPILE_DEFINITIONS>
GL_CONTEXT_PROFILE=${KTX_GL_CONTEXT_PROFILE}
GL_CONTEXT_MAJOR_VERSION=${KTX_GL_CONTEXT_MAJOR_VERSION}
GL_CONTEXT_MINOR_VERSION=${KTX_GL_CONTEXT_MINOR_VERSION}
$<$<PLATFORM_ID:Windows>:NOMINMAX>
)
else()
target_compile_definitions(
${target}
PRIVATE
$<TARGET_PROPERTY:ktx,INTERFACE_COMPILE_DEFINITIONS>
$<$<PLATFORM_ID:Windows>:NOMINMAX>
)
endif()
set_target_properties( ${target} PROPERTIES RESOURCE "${resources}" )
if(APPLE)
set(product_name "${target}")
set( bundle_identifier org.khronos.ktx.${product_name} )
# This property must be set to avoid an Xcode warning.
set_target_properties(${target} PROPERTIES XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "org.khronos.ktx.${product_name}")
# See important comment about MACOSX_BUNDLE_INFO_PLIST and these
# properties in ./vkloadtests.cmake.
set_target_properties( ${target} PROPERTIES
MACOSX_BUNDLE_BUNDLE_NAME ${product_name}
MACOSX_BUNDLE_EXECUTABLE_NAME ${product_name}
MACOSX_BUNDLE_COPYRIGHT "© 2024 Khronos Group, Inc."
MACOSX_BUNDLE_GUI_IDENTIFIER ${bundle_identifier}
MACOSX_BUNDLE_INFO_PLIST ${INFO_PLIST_IN}
MACOSX_BUNDLE_INFO_STRING "View KTX textures; display via OpenGL."
MACOSX_BUNDLE_ICON_FILE ${KTX_APP_ICON}
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION}
# Because libassimp is built with bitcode disabled. It's not
# important unless submitting to the App Store and currently
# bitcode is optional.
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH "YES"
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME ${KTX_APP_ICON_BASENAME}
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2" # iPhone and iPad
)
unset(product_name)
unset(bundle_identifier)
# The generated project code for building an Apple bundle automatically
# copies the executable and all files with the RESOURCE property to the
# bundle adjusting for the difference in bundle layout between iOS &
# macOS.
if(NOT IOS)
set_target_properties( ${target} PROPERTIES
INSTALL_RPATH "@executable_path/../Frameworks"
)
if(BUILD_SHARED_LIBS)
add_custom_command( TARGET ${target} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:ktx> "$<TARGET_BUNDLE_CONTENT_DIR:${target}>/Frameworks/$<TARGET_FILE_NAME:ktx>"
COMMAND ${CMAKE_COMMAND} -E create_symlink $<TARGET_FILE_NAME:ktx> "$<TARGET_BUNDLE_CONTENT_DIR:${target}>/Frameworks/$<TARGET_SONAME_FILE_NAME:ktx>"
COMMENT "Copy KTX library to build destination"
)
endif()
# Re. SDL3 & assimp: no copy required.: vcpkg libs are static or else
# vcpkg arranges copy. Brew libs cannot be bundled.
# Specify destination for cmake --install.
install(TARGETS ${target}
BUNDLE
DESTINATION /Applications
COMPONENT GlLoadTestApps
)
endif()
else()
if(EMSCRIPTEN)
set_target_properties(${target} PROPERTIES SUFFIX ".html")
endif()
# This copies the resources next to the executable for ease
# of use during debugging and testing.
add_custom_command( TARGET ${target} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory
$<TARGET_FILE_DIR:${target}>/../resources
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${resources}
$<TARGET_FILE_DIR:${target}>/../resources
)
# See important comment and TODO:s starting at line 365
# in ./vkloadtests.cmake regarding installation of these
# targets. Search for "keep the resources".
set_target_properties( ${target} PROPERTIES
INSTALL_RPATH "\$ORIGIN;${CMAKE_INSTALL_FULL_LIBDIR}"
)
######### IMPORTANT ######
# When installing via `cmake --install` ALSO install the
# library component. There seems no way to make a dependency.
##########################
# set( destroot "${LOAD_TEST_DESTROOT}/$<TARGET_FILE_NAME:${target}>")
# NOTE: WHEN RUNNING MANUAL INSTALLS INSTALL library COMPONENT TOO.
# install(TARGETS ${target}
# RUNTIME
# DESTINATION ${destroot}/bin
# COMPONENT GlLoadTestApps
# LIBRARY
# DESTINATION ${CMAKE_INSTALL_LIBDIR}
# COMPONENT GlLoadTestApps
# RESOURCE
# DESTINATION ${destroot}/resources
# COMPONENT GlLoadTestApps
# )
# install(TARGETS ktx
# RUNTIME
# DESTINATION ${destroot}/bin
# COMPONENT GlLoadTestApps
# LIBRARY
# DESTINATION ${destroot}/lib
# COMPONENT GlLoadTestApps
# )
# if(LINUX)
# # Add a link from the regular bin directory to put command
# # on PATH.
# install(CODE "
# EXECUTE_PROCESS(COMMAND ln -s ${destroot}/bin/$<TARGET_FILE_NAME:${target}> ${CMAKE_INSTALL_FULL_BINDIR}
# )"
# COMPONENT GlLoadTestApps
# )
# install(FILES
# ${CMAKE_CURRENT_BINARY_DIR}/${target}.desktop
# DESTINATION /usr/share/applications
# COMPONENT GlLoadTestApps
# )
# endif(LINUX)
endif()
endfunction( create_gl_target target )
set( ES1_TEST_IMAGES
no-npot.ktx
hi_mark.ktx
luminance-reference-metadata.ktx
orient-up-metadata.ktx
orient-down-metadata.ktx
etc1.ktx
etc2-rgb.ktx
etc2-rgba1.ktx
etc2-rgba8.ktx
rgba-reference.ktx
rgb-reference.ktx
rgb-amg-reference.ktx
rgb-mipmap-reference.ktx
hi_mark_sq.ktx
)
list( TRANSFORM ES1_TEST_IMAGES
PREPEND "${PROJECT_SOURCE_DIR}/tests/testimages/"
)
set( ES1_SOURCES
glloadtests/gles1/ES1LoadTests.cpp
glloadtests/gles1/DrawTexture.cpp
glloadtests/gles1/DrawTexture.h
glloadtests/gles1/TexturedCube.cpp
glloadtests/gles1/TexturedCube.h
)
set( GL3_TEST_IMAGES
etc1s_Iron_Bars_001_normal.ktx2
uastc_Iron_Bars_001_normal.ktx2
color_grid_uastc_zstd.ktx2
color_grid_zstd.ktx2
color_grid_uastc.ktx2
color_grid_basis.ktx2
kodim17_basis.ktx2
kodim17_basis.ktx2
FlightHelmet_baseColor_basis.ktx2
rgba-reference-u.ktx2
rgba-reference-u.ktx2
rgba-reference-u.ktx2
cubemap_goldengate_uastc_rdo4_zstd5_rd.ktx2
cubemap_yokohama_basis_rd.ktx2
orient-down-metadata-u.ktx2
orient-down-metadata-u.ktx2
texturearray_bc3_unorm.ktx2
texturearray_astc_8x8_unorm.ktx2
texturearray_etc2_unorm.ktx2
3dtex_7_reference_u.ktx2
rgb-mipmap-reference-u.ktx2
hi_mark.ktx
orient-up-metadata.ktx
orient-down-metadata.ktx
not4_rgb888_srgb.ktx
etc1.ktx
etc2-rgb.ktx
etc2-rgba1.ktx
etc2-rgba8.ktx
etc2-sRGB.ktx
etc2-sRGBa1.ktx
etc2-sRGBa8.ktx
rgba-reference.ktx
rgb-reference.ktx
conftestimage_R11_EAC.ktx
conftestimage_SIGNED_R11_EAC.ktx
conftestimage_RG11_EAC.ktx
conftestimage_SIGNED_RG11_EAC.ktx
texturearray_bc3_unorm.ktx
texturearray_astc_8x8_unorm.ktx
texturearray_etc2_unorm.ktx
rgb-amg-reference.ktx
rgb-mipmap-reference.ktx
hi_mark_sq.ktx
)
list( TRANSFORM GL3_TEST_IMAGES
PREPEND "${PROJECT_SOURCE_DIR}/tests/testimages/"
)
set( GL3_RESOURCE_FILES ${LOAD_TEST_COMMON_RESOURCE_FILES} ${GL3_TEST_IMAGES} )
set( GL3_SOURCES
common/TranscodeTargetStrToFmt.cpp
common/TranscodeTargetStrToFmt.h
common/disable_glm_warnings.h
common/reenable_warnings.h
glloadtests/shader-based/DrawTexture.cpp
glloadtests/shader-based/DrawTexture.h
glloadtests/shader-based/EncodeTexture.cpp
glloadtests/shader-based/EncodeTexture.h
glloadtests/shader-based/GL3LoadTests.cpp
glloadtests/shader-based/GL3LoadTestSample.cpp
glloadtests/shader-based/GL3LoadTestSample.h
glloadtests/shader-based/InstancedSampleBase.cpp
glloadtests/shader-based/InstancedSampleBase.h
glloadtests/shader-based/mygl.h
glloadtests/shader-based/shaders.cpp
glloadtests/shader-based/Texture3d.cpp
glloadtests/shader-based/Texture3d.h
glloadtests/shader-based/TextureArray.cpp
glloadtests/shader-based/TextureArray.h
glloadtests/shader-based/TextureMipmap.cpp
glloadtests/shader-based/TextureMipmap.h
glloadtests/shader-based/TextureCubemap.cpp
glloadtests/shader-based/TextureCubemap.h
glloadtests/shader-based/TexturedCube.cpp
glloadtests/shader-based/TexturedCube.h
glloadtests/utils/GLMeshLoader.hpp
glloadtests/utils/GLTextureTranscoder.hpp
)
if(WIN32)
if(NOT OPENGL_ES_EMULATOR)
message("OPENGL_ES_EMULATOR not set. Will not build OpenGL ES load tests applications.")
else()
set(EMULATE_GLES ON)
endif()
endif()
if(IOS OR EMULATE_GLES)
# OpenGL ES 1.0
create_gl_target( es1loadtests "ES1" "${ES1_SOURCES}" "${KTX_APP_ICON_PATH}" "${ES1_TEST_IMAGES}" SDL_GL_CONTEXT_PROFILE_ES 1 0 ON)
endif()
if(IOS OR EMSCRIPTEN OR EMULATE_GLES)
# OpenGL ES 3.0
create_gl_target( es3loadtests "ES3" "${GL3_SOURCES}" "${LOAD_TEST_COMMON_RESOURCE_FILES}" "${GL3_TEST_IMAGES}" SDL_GL_CONTEXT_PROFILE_ES 3 0 ON YES)
endif()
if( (APPLE AND NOT IOS) OR LINUX OR WIN32 )
# OpenGL 3.3
create_gl_target( gl3loadtests "GL3" "${GL3_SOURCES}" "${LOAD_TEST_COMMON_RESOURCE_FILES}" "${GL3_TEST_IMAGES}" SDL_GL_CONTEXT_PROFILE_CORE 3 3 OFF YES)
endif()
@@ -0,0 +1,313 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2015-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief Implementation of app for running a set of OpenGL load tests.
*/
#include <exception>
#include <sstream>
#include <ktx.h>
#include "GLLoadTests.h"
#include "ltexceptions.h"
GLLoadTests::GLLoadTests(const sampleInvocation samples[],
const uint32_t numSamples,
const char* const name,
const SDL_GLProfile profile,
const int majorVersion,
const int minorVersion)
: GLAppSDL(name, 640, 480, profile, majorVersion, minorVersion),
siSamples(samples), sampleIndex(numSamples)
{
pCurSample = nullptr;
}
GLLoadTests::~GLLoadTests()
{
delete pCurSample;
}
bool
GLLoadTests::initialize(Args& args)
{
bool explicitlyLoadOpenGL = false;
for (uint32_t i = 1; i < args.size(); i++) {
if (args[i].compare("--load-gl") == 0) {
explicitlyLoadOpenGL = true;
args.erase(args.begin() + i);
break;
}
}
if (!GLAppSDL::initialize(args))
return false;
// --load-gl allows testing of explicitly loading the OpenGL pointers.
// Default is for ktxTexture_GLUpload to implicitly load them.
if (explicitlyLoadOpenGL)
ktxLoadOpenGL(reinterpret_cast<PFNGLGETPROCADDRESS>(SDL_GL_GetProcAddress));
for (auto it = args.begin() + 1; it != args.end(); it++) {
infiles.push_back(*it);
}
if (infiles.size() > 0) {
sampleIndex.setNumSamples((uint32_t)infiles.size());
}
// Launch the first sample.
invokeSample(Direction::eForward);
return true;
}
void
GLLoadTests::finalize()
{
delete pCurSample;
GLAppSDL::finalize();
}
bool
GLLoadTests::doEvent(SDL_Event* event)
{
bool result = false;
switch (event->type) {
case SDL_EVENT_KEY_UP:
switch (event->key.key) {
case 'q':
quit = true;
break;
case 'n':
++sampleIndex;
invokeSample(Direction::eForward);
break;
case 'p':
--sampleIndex;
invokeSample(Direction::eBack);
break;
default:
result = true;
}
break;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
// Forward to sample in case this is the start of motion.
result = true;
switch (event->button.button) {
case SDL_BUTTON_LEFT:
buttonDown.x = event->button.x;
buttonDown.y = event->button.y;
buttonDown.timestamp = event->button.timestamp;
break;
default:
break;
}
break;
case SDL_EVENT_MOUSE_BUTTON_UP:
// Forward to sample so it doesn't get stuck in button down state.
result = true;
switch (event->button.button) {
case SDL_BUTTON_LEFT:
if (SDL_fabs(event->button.x - buttonDown.x) < 5
&& SDL_fabs(event->button.y - buttonDown.y) < 5
&& (event->button.timestamp - buttonDown.timestamp) < 100) {
// Advance to the next sample.
++sampleIndex;
invokeSample(Direction::eForward);
}
break;
default:
break;
}
break;
// On macOS drop events come also when Launch Pad sends a file open event.
case SDL_EVENT_DROP_BEGIN:
// Opens of multiple selected files from Finder/LaunchPad come as
// a BEGIN, COMPLETE sequence per file. Only clear infiles after a
// suitable pause between COMPLETE and BEGIN.
if (event->drop.timestamp - dropCompleteTime > 500) {
infiles.clear();
}
break;
case SDL_EVENT_DROP_FILE:
infiles.push_back(event->drop.data);
break;
case SDL_EVENT_DROP_COMPLETE:
if (!infiles.empty()) {
// Guard against the drop being text.
dropCompleteTime = event->drop.timestamp;
sampleIndex.setNumSamples((uint32_t)infiles.size());
invokeSample(Direction::eForward);
}
break;
case SDL_EVENT_USER:
if (event->user.code == SwipeDetector::swipeGesture) {
// This is horrible.
uint64_t udirection = reinterpret_cast<uint64_t>(event->user.data1);
SwipeDetector::Direction direction =
static_cast<SwipeDetector::Direction>(udirection);
switch (direction) {
case SwipeDetector::Direction::left:
++sampleIndex;
invokeSample(Direction::eForward);
break;
case SwipeDetector::Direction::right:
--sampleIndex;
invokeSample(Direction::eBack);
break;
default:
result = true;
}
} else {
result = true;
}
break;
default:
result = swipeDetector.doEvent(event);
}
if (result) {
// Further processing required.
if (pCurSample != nullptr)
result = pCurSample->doEvent(event); // Give sample a chance.
if (result)
return GLAppSDL::doEvent(event); // Pass to base class.
}
return result;
}
void
GLLoadTests::windowResized()
{
if (pCurSample != nullptr)
pCurSample->resize(w_width, w_height);
}
void
GLLoadTests::drawFrame(uint32_t msTicks)
{
if (pCurSample != nullptr)
pCurSample->run(msTicks);
GLAppSDL::drawFrame(msTicks);
}
#if 0
void
GLLoadTests::getOverlayText(GLTextOverlay * textOverlay)
{
if (enableTextOverlay && pCurSample != nullptr) {
pCurSample->getOverlayText(textOverlay);
}
}
#endif
void
GLLoadTests::invokeSample(Direction dir)
{
const sampleInvocation* sampleInv = &siSamples[sampleIndex];
delete pCurSample;
// Certain events can be triggered during new sample initialization
// while pCurSample is not valid, e.g. FOCUS_LOST. Protect against
// problems from this by indicating there is no current sample.
pCurSample = nullptr;
uint32_t unsupportedTypeExceptions = 0;
std::string fileTitle;
for (;;) {
try {
if (infiles.size() > 0) {
fileTitle = "Viewing file ";
fileTitle += infiles[sampleIndex];
pCurSample = showFile(infiles[sampleIndex]);
} else {
sampleInv = &siSamples[sampleIndex];
pCurSample = sampleInv->createSample(w_width, w_height,
sampleInv->args,
sBasePath);
}
break;
} catch (unsupported_ctype& e) {
(void)e; // To quiet unused variable warnings from some compilers.
unsupportedTypeExceptions++;
if (unsupportedTypeExceptions == sampleIndex.getNumSamples()) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
infiles.size() > 0 ? fileTitle.c_str() : sampleInv->title,
"None of the specified samples or files use texture types "
"supported on this platform.",
NULL);
exit(0);
} else {
dir == Direction::eForward ? ++sampleIndex : --sampleIndex;
}
} catch (std::exception& e) {
const SDL_MessageBoxButtonData buttons[] = {
/* .flags, .buttonid, .text */
{ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 0, "Continue" },
{ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, 1, "Abort" },
};
#if 0
const SDL_MessageBoxColorScheme colorScheme = {
{ /* .colors (.r, .g, .b) */
/* [SDL_MESSAGEBOX_COLOR_BACKGROUND] */
{ 255, 0, 0 },
/* [SDL_MESSAGEBOX_COLOR_TEXT] */
{ 0, 255, 0 },
/* [SDL_MESSAGEBOX_COLOR_BUTTON_BORDER] */
{ 255, 255, 0 },
/* [SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND] */
{ 0, 0, 255 },
/* [SDL_MESSAGEBOX_COLOR_BUTTON_SELECTED] */
{ 255, 0, 255 }
}
};
#endif
const SDL_MessageBoxData messageboxdata = {
SDL_MESSAGEBOX_ERROR, // .flags
NULL, // .window
infiles.size() > 0 ?
fileTitle.c_str() : sampleInv->title, // .title
e.what(), // .message
SDL_arraysize(buttons), // .numbuttons
buttons, // .buttons
NULL //&colorScheme // .colorScheme
};
int buttonid;
if (!SDL_ShowMessageBox(&messageboxdata, &buttonid)) {
SDL_Log("error displaying error message box");
exit(1);
}
if (buttonid == 0) {
// Continue
dir == Direction::eForward ? ++sampleIndex : --sampleIndex;
} else {
// We've been told to quit or no button was pressed.
exit(1);
}
}
}
setAppTitle(infiles.size() > 0 ? fileTitle.c_str() : sampleInv->title);
pCurSample->resize(w_width, w_height);
}
void
GLLoadTests::onFPSUpdate()
{
// Using onFPSUpdate avoids rewriting the title every frame.
//setWindowTitle(pCurSample->title);
GLAppSDL::onFPSUpdate();
}
@@ -0,0 +1,100 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
#ifndef GL_LOAD_TESTS_H
#define GL_LOAD_TESTS_H
/*
* Copyright 2015-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief Definition of app for running a set of OpenGL load tests.
*/
#include <string>
#include <vector>
#include "GLAppSDL.h"
#include "LoadTestSample.h"
#include "SwipeDetector.h"
class GLLoadTests : public GLAppSDL {
public:
/** A table of samples and arguments */
typedef struct sampleInvocation_ {
const LoadTestSample::PFN_create createSample;
const char* const args;
const char* const title;
} sampleInvocation;
GLLoadTests(const sampleInvocation samples[],
const uint32_t numSamples,
const char* const name,
const SDL_GLProfile profile,
const int majorVersion,
const int minorVersion);
virtual ~GLLoadTests();
virtual bool doEvent(SDL_Event* event);
virtual void drawFrame(uint32_t msTicks);
virtual void finalize();
//virtual void getOverlayText(TextOverlay* textOverlay, float yOffset);
virtual bool initialize(Args& args);
virtual void onFPSUpdate();
virtual void windowResized();
protected:
enum class Direction {
eForward,
eBack
};
void invokeSample(Direction dir);
LoadTestSample* showFile(const std::string& filename);
LoadTestSample* pCurSample;
bool quit = false;
const sampleInvocation* const siSamples;
class sampleIndex {
public:
sampleIndex(const uint32_t numSamples) : numSamples(numSamples) {
index = 0;
}
sampleIndex& operator++() {
if (++index >= numSamples)
index = 0;
return *this;
}
sampleIndex& operator--() {
if (--index > numSamples /* underflow */)
index = numSamples-1;
return *this;
}
operator int32_t() {
return index;
}
uint32_t getNumSamples() { return numSamples; }
void setNumSamples(uint32_t ns) { numSamples = ns; }
protected:
uint32_t numSamples;
uint32_t index;
} sampleIndex;
std::vector<std::string> infiles;
Sint64 dropCompleteTime = 0;
struct {
float x;
float y;
Sint64 timestamp;
} buttonDown;
SwipeDetector swipeDetector;
};
#endif /* GL_LOAD_TESTS_H */
@@ -0,0 +1,250 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2018-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file DrawTexture.cpp
* @brief Draw textures at actual size using the DrawTexture functions
* from OES_draw_texture.
*
* @author Mark Callow
*/
#if defined(_WIN32)
#if _MSC_VER < 1900
#define snprintf _snprintf
#endif
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <iomanip>
#include <sstream>
#include <ktx.h>
#include "disable_glm_warnings.h"
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include "reenable_warnings.h"
#include "DrawTexture.h"
#include "frame.h"
/* ------------------------------------------------------------------------ */
#if 0
static int isPowerOfTwo (int x)
{
if (x < 0) x = -1;
return ((x != 0) && !(x & (x - 1)));
}
#endif
/* ------------------------------------------------------------------------ */
LoadTestSample*
DrawTexture::create(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath)
{
return new DrawTexture(width, height, szArgs, sBasePath);
}
DrawTexture::DrawTexture(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath)
: LoadTestSample(width, height, sBasePath)
{
GLint iCropRect[4] = {0, 0, 0, 0};
const GLchar* szExtensions = (const GLchar*)glGetString(GL_EXTENSIONS);
const char* filename;
std::string pathname;
GLenum target;
GLboolean npotTexture;
GLenum glerror;
ktxTexture* kTexture;
KTX_error_code ktxresult;
GLint sign_s = 1, sign_t = 1;
bInitialized = false;
gnTexture = 0;
if (strstr(szExtensions, "OES_draw_texture") != NULL) {
glDrawTexsOES = (PFNGLDRAWTEXSOESPROC)SDL_GL_GetProcAddress("glDrawTexsOES");
glDrawTexiOES = (PFNGLDRAWTEXIOESPROC)SDL_GL_GetProcAddress("glDrawTexiOES");
glDrawTexxOES = (PFNGLDRAWTEXXOESPROC)SDL_GL_GetProcAddress("glDrawTexxOES");
glDrawTexfOES = (PFNGLDRAWTEXFOESPROC)SDL_GL_GetProcAddress("glDrawTexfOES");
glDrawTexsvOES = (PFNGLDRAWTEXSVOESPROC)SDL_GL_GetProcAddress("glDrawTexsvOES");
glDrawTexivOES = (PFNGLDRAWTEXIVOESPROC)SDL_GL_GetProcAddress("glDrawTexivOES");
glDrawTexxvOES = (PFNGLDRAWTEXXVOESPROC)SDL_GL_GetProcAddress("glDrawTexxvOES");
glDrawTexfvOES = (PFNGLDRAWTEXFVOESPROC)SDL_GL_GetProcAddress("glDrawTexfvOES");
} else {
/* Can't do anything */
std::stringstream message;
message << "DrawTexture: this OpenGL ES implementation does not support"
<< " OES_draw_texture. Can't Run Test";
throw std::runtime_error(message.str());
}
if (strstr(szExtensions, "OES_texture_npot") != NULL)
bNpotSupported = GL_TRUE;
else
bNpotSupported = GL_FALSE;
if ((filename = strchr(szArgs, ' ')) != NULL) {
npotTexture = GL_FALSE;
if (!strncmp(szArgs, "--npot ", 7)) {
npotTexture = GL_TRUE;
#if defined(DEBUG)
} else {
assert(0); /* Unknown argument in sampleInvocations */
#endif
}
} else {
filename = szArgs;
npotTexture = GL_FALSE;
}
if (npotTexture && !bNpotSupported) {
/* Load error texture. */
filename = "no-npot.ktx";
}
pathname = getAssetPath() + filename;
ktxresult = ktxTexture_CreateFromNamedFile(pathname.c_str(),
KTX_TEXTURE_CREATE_NO_FLAGS,
&kTexture);
if (KTX_SUCCESS != ktxresult) {
std::stringstream message;
message << "Creation of ktxTexture from \"" << pathname
<< "\" failed: " << ktxErrorString(ktxresult);
throw std::runtime_error(message.str());
}
ktxresult = ktxTexture_GLUpload(kTexture, &gnTexture, &target, &glerror);
if (KTX_SUCCESS == ktxresult) {
if (target != GL_TEXTURE_2D) {
/* Can only draw 2D textures */
glDeleteTextures(1, &gnTexture);
return;
}
if (kTexture->orientation.x == KTX_ORIENT_X_LEFT)
sign_s = -1;
if (kTexture->orientation.y == KTX_ORIENT_Y_DOWN)
sign_t = -1;
iCropRect[2] = uTexWidth = kTexture->baseWidth;
iCropRect[3] = uTexHeight = kTexture->baseHeight;
iCropRect[2] *= sign_s;
iCropRect[3] *= sign_t;
glEnable(target);
if (kTexture->numLevels > 1)
// Enable bilinear mipmapping.
// TO DO: application can consider inserting a key,value pair in
// the KTX file that indicates what type of filtering to use.
glTexParameteri(target,
GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
else
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
glTexParameteriv(target, GL_TEXTURE_CROP_RECT_OES, iCropRect);
/* Check for any errors */
assert(GL_NO_ERROR == glGetError());
ktxTexture_Destroy(kTexture);
} else {
std::stringstream message;
message << "Load of texture from \"" << pathname << "\" failed: ";
if (ktxresult == KTX_GL_ERROR) {
message << std::showbase << "GL error " << std::hex << glerror
<< "occurred.";
} else {
message << ktxErrorString(ktxresult);
}
throw std::runtime_error(message.str());
}
glClearColor(0.4f, 0.4f, 0.5f, 1.0f);
glColor4f(1.0f, 1.0f, 0.0f, 1.0f);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_BYTE, 0, (GLvoid *)frame_position);
bInitialized = GL_TRUE;
}
/* ------------------------------------------------------------------------ */
DrawTexture::~DrawTexture()
{
if (bInitialized) {
glDeleteTextures(1, &gnTexture);
}
assert(GL_NO_ERROR == glGetError());
}
/* ------------------------------------------------------------------------ */
void
DrawTexture::resize(uint32_t uNewWidth, uint32_t uNewHeight)
{
glViewport(0, 0, uNewWidth, uNewHeight);
this->uWidth = uNewWidth;
this->uHeight = uNewHeight;
// Set up an orthographic projection where 1 = 1 pixel
framePMatrix = glm::ortho(0.f, (float)uWidth, 0.f, (float)uHeight);
// Move (0,0,0) to the center of the window.
framePMatrix *= glm::translate(glm::mat4(),
glm::vec3((float)uWidth/2, (float)uHeight/2, 0));
glMatrixMode(GL_PROJECTION);
glLoadMatrixf(glm::value_ptr(framePMatrix));
glMatrixMode(GL_MODELVIEW);
// Scale the frame to fill the viewport. To guarantee its lines
// appear we need to inset them by half-a-pixel hence the -1.
// [Lines at the edges of the clip volume may or may not appear
// depending on the OpenGL ES implementation. This is because
// (a) the edges are on the points of the diamonds of the diamond
// exit rule and slight precision errors can easily push the
// lines outside the diamonds.
// (b) the specification allows lines to be up to 1 pixel either
// side of the exact position.]
glLoadIdentity();
glScalef((float)(uWidth - 1) / 2, (float)(uHeight - 1) / 2, 1);
}
/* ------------------------------------------------------------------------ */
void
DrawTexture::run(uint32_t /*msTicks*/)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDisable(GL_TEXTURE_2D);
glDrawArrays(GL_LINE_LOOP, 0, 4);
glEnable(GL_TEXTURE_2D);
glDrawTexiOES(uWidth/2 - uTexWidth/2,
(uHeight)/2 - uTexHeight/2,
0,
uTexWidth, uTexHeight);
assert(GL_NO_ERROR == glGetError());
}
/* ------------------------------------------------------------------------ */
@@ -0,0 +1,66 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2018-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file DrawTexture.h
* @brief Draw textures at actual size using the DrawTexture functions
* from OES_draw_texture.
*
* @author Mark Callow
*/
#ifndef DRAW_TEXTURE_H
#define DRAW_TEXTURE_H
#include <GLES/gl.h>
#include <GLES/glext.h>
#include "LoadTestSample.h"
class DrawTexture : public LoadTestSample {
public:
DrawTexture(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath);
~DrawTexture();
virtual void resize(uint32_t width, uint32_t height);
virtual void run(uint32_t msTicks);
//virtual void getOverlayText(GLTextOverlay *textOverlay);
static LoadTestSample*
create(uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath);
protected:
PFNGLDRAWTEXSOESPROC glDrawTexsOES;
PFNGLDRAWTEXIOESPROC glDrawTexiOES;
PFNGLDRAWTEXXOESPROC glDrawTexxOES;
PFNGLDRAWTEXFOESPROC glDrawTexfOES;
PFNGLDRAWTEXSVOESPROC glDrawTexsvOES;
PFNGLDRAWTEXIVOESPROC glDrawTexivOES;
PFNGLDRAWTEXXVOESPROC glDrawTexxvOES;
PFNGLDRAWTEXFVOESPROC glDrawTexfvOES;
uint32_t uWidth;
uint32_t uHeight;
uint32_t uTexWidth;
uint32_t uTexHeight;
glm::mat4 framePMatrix;
GLuint gnTexture;
bool bNpotSupported;
bool bInitialized;
};
#endif /* DRAW_TEXTURE_H */
@@ -0,0 +1,109 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2018-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file ES1LoadTests.cpp
* @brief List of tests of the KTX loader for OpenGL ES 1.1.
*
* The loader is tested by loading and drawing KTX textures in various formats
* using the DrawTexture and TexturedCube samples.
*/
#include <sstream>
#include <ktx.h>
#include "GLLoadTests.h"
#include "DrawTexture.h"
#include "TexturedCube.h"
LoadTestSample*
GLLoadTests::showFile(const std::string& filename)
{
KTX_error_code ktxresult;
ktxTexture* kTexture;
ktxresult = ktxTexture_CreateFromNamedFile(filename.c_str(),
KTX_TEXTURE_CREATE_NO_FLAGS,
&kTexture);
if (KTX_SUCCESS != ktxresult) {
std::stringstream message;
message << "Creation of ktxTexture from \"" << getAssetPath()
<< filename << "\" failed: " << ktxErrorString(ktxresult);
throw std::runtime_error(message.str());
}
LoadTestSample::PFN_create createViewer;
LoadTestSample* pViewer;
createViewer = DrawTexture::create;
ktxTexture_Destroy(kTexture);
pViewer = createViewer(w_width, w_height, filename.c_str(), "");
return pViewer;
}
const GLLoadTests::sampleInvocation siSamples[] = {
{ DrawTexture::create,
"--npot hi_mark.ktx",
"RGB8 NPOT HI Logo"
},
{ DrawTexture::create,
"--npot luminance-reference-metadata.ktx",
"LUMINANCE8 NPOT"
},
{ DrawTexture::create,
"orient-up-metadata.ktx",
"RGB8 + KTXOrientation up"
},
{ DrawTexture::create,
"orient-down-metadata.ktx",
"RGB8 + KTXOrientation down"
},
{ DrawTexture::create,
"etc1.ktx",
"ETC1 RGB8"
},
{ DrawTexture::create,
"etc2-rgb.ktx",
"ETC2 RGB8"
},
{ DrawTexture::create,
"etc2-rgba1.ktx",
"ETC2 RGB8A1"
},
{ DrawTexture::create,
"etc2-rgba8.ktx",
"ETC2 RGB8A8"
},
{ DrawTexture::create,
"rgba-reference.ktx",
"RGBA8"
},
{ TexturedCube::create,
"rgb-reference.ktx",
"RGB8"
},
{ TexturedCube::create,
"rgb-amg-reference.ktx",
"RGB8 + Auto Mipmap"
},
{ TexturedCube::create,
"rgb-mipmap-reference.ktx",
"RGB8 Color/level mipmap"
},
{ TexturedCube::create,
"--npot hi_mark_sq.ktx",
"RGB8 NPOT HI Logo"
},
};
const int iNumSamples = sizeof(siSamples) / sizeof(GLLoadTests::sampleInvocation);
AppBaseSDL* theApp = new GLLoadTests(siSamples, iNumSamples,
"KTX Loader Tests for OpenGL ES 1",
SDL_GL_CONTEXT_PROFILE_ES, 1, 1);
@@ -0,0 +1,202 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2018-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file TexturedCube.cpp
* @brief Draw a textured cube.
*/
#if defined(_WIN32)
#if _MSC_VER < 1900
#define snprintf _snprintf
#endif
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <iomanip>
#include <sstream>
#include <ktx.h>
#include "disable_glm_warnings.h"
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include "reenable_warnings.h"
#include "TexturedCube.h"
#include "cube.h"
#if defined(_WIN32)
#define snprintf _snprintf
#endif
/* ------------------------------------------------------------------------- */
LoadTestSample*
TexturedCube::create(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath)
{
return new TexturedCube(width, height, szArgs, sBasePath);
}
TexturedCube::TexturedCube(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath)
: LoadTestSample(width, height, sBasePath)
{
const GLchar* szExtensions = (const GLchar*)glGetString(GL_EXTENSIONS);
const char* filename;
std::string pathname;
GLuint gnTexture = 0;
GLenum target;
GLenum glerror;
GLboolean npotSupported, npotTexture;
ktxTexture* kTexture;
KTX_error_code ktxresult;
if (strstr(szExtensions, "OES_texture_npot") != NULL)
npotSupported = GL_TRUE;
else
npotSupported = GL_FALSE;
if ((filename = strchr(szArgs, ' ')) != NULL) {
npotTexture = GL_FALSE;
if (!strncmp(szArgs, "--npot ", 7)) {
npotTexture = GL_TRUE;
#if defined(DEBUG)
} else {
assert(0); /* Unknown argument in sampleInvocations */
#endif
}
} else {
filename = szArgs;
npotTexture = GL_FALSE;
}
if (npotTexture && !npotSupported) {
/* Load error texture. */
filename = "no-npot.ktx";
}
pathname = getAssetPath() + filename;
ktxresult = ktxTexture_CreateFromNamedFile(pathname.c_str(),
KTX_TEXTURE_CREATE_NO_FLAGS,
&kTexture);
if (KTX_SUCCESS != ktxresult) {
std::stringstream message;
message << "Creation of ktxTexture from \"" << pathname
<< "\" failed: " << ktxErrorString(ktxresult);
throw std::runtime_error(message.str());
}
ktxresult = ktxTexture_GLUpload(kTexture, &gnTexture, &target, &glerror);
if (KTX_SUCCESS == ktxresult) {
if (target != GL_TEXTURE_2D) {
/* Can only draw 2D textures */
glDeleteTextures(1, &gnTexture);
return;
}
glEnable(target);
if (kTexture->numLevels > 1)
// Enable bilinear mipmapping.
// TO DO: application can consider inserting a key,value pair in
// the KTX file that indicates what type of filtering to use.
glTexParameteri(target,
GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
else
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
ktxTexture_Destroy(kTexture);
} else {
std::stringstream message;
message << "Load of texture from \"" << pathname << "\" failed: ";
if (ktxresult == KTX_GL_ERROR) {
message << std::showbase << "GL error " << std::hex << glerror
<< "occurred.";
} else {
message << ktxErrorString(ktxresult);
}
throw std::runtime_error(message.str());
}
/* By default dithering is enabled. Dithering does not provide visual
* improvement in this sample so disable it to improve performance.
*/
glDisable(GL_DITHER);
glEnable(GL_CULL_FACE);
glClearColor(0.2f,0.3f,0.4f,1.0f);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, cube_face);
glColorPointer(4, GL_FLOAT, 0, cube_color);
glTexCoordPointer(2, GL_FLOAT, 0, cube_texture);
}
TexturedCube::~TexturedCube()
{
glDisable(GL_TEXTURE_2D);
glEnable(GL_DITHER);
glDisable(GL_CULL_FACE);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
assert(GL_NO_ERROR == glGetError());
}
void
TexturedCube::resize(uint32_t uWidth, uint32_t uHeight)
{
glm::mat4 matProj;
glViewport(0, 0, uWidth, uHeight);
glMatrixMode( GL_PROJECTION );
matProj = glm::perspective(glm::radians(45.f),
uWidth / (float)uHeight,
1.f, 100.f);
glLoadIdentity();
glLoadMatrixf(glm::value_ptr(matProj));
glMatrixMode( GL_MODELVIEW );
assert(GL_NO_ERROR == glGetError());
}
void
TexturedCube::run(uint32_t msTicks)
{
/* Setup the view matrix : just turn around the cube. */
const float fDistance = 5.0f;
glm::vec3 eye((float)cos( msTicks*0.001f ) * fDistance,
(float)sin( msTicks*0.0007f ) * fDistance,
(float)sin( msTicks*0.001f ) * fDistance);
glm::vec3 look(0.,0.,0.);
glm::vec3 up(0.,1.,0.);
glm::mat4 matView = glm::lookAt( eye, look, up );
glLoadIdentity();
glLoadMatrixf(glm::value_ptr(matView));
/* Draw */
glClear( GL_COLOR_BUFFER_BIT );
glDrawElements(GL_TRIANGLES, CUBE_NUM_INDICES,
GL_UNSIGNED_SHORT, cube_index_buffer);
assert(GL_NO_ERROR == glGetError());
}
/* ------------------------------------------------------------------------- */
@@ -0,0 +1,38 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2018-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file TexturedCube.cpp
* @brief Draw a textured cube.
*/
#ifndef TEXTURED_CUBE_H
#define TEXTURED_CUBE_H
#include <GLES/gl.h>
#include <GLES/glext.h>
#include "LoadTestSample.h"
class TexturedCube : public LoadTestSample {
public:
TexturedCube(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath);
~TexturedCube();
virtual void resize(uint32_t width, uint32_t height);
virtual void run(uint32_t msTicks);
//virtual void getOverlayText(GLTextOverlay *textOverlay);
static LoadTestSample*
create(uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath);
};
#endif /* TEXTURED_CUBE_H */
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2015-2024 Mark Callow
SPDX-License-Identifier: Apache-2.0
WARNING: Do not attempt to edit the Info.plist you see in the Xcode
project. It is unavoidably configured from this by CMake. Edit here.
-->
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
<key>CFBundleDisplayName</key>
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
<key>CFBundleGetInfoString</key>
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
<key>NSHumanReadableCopyright</key>
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleVersion</key>
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
<key>CFBundleShortVersionString</key>
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>NSMainNibFile</key>
<string></string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIApplicationSupportsIndirectInputEvents</key>
<string>YES</string>
</dict>
</plist>
@@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
@@ -0,0 +1,57 @@
{
"images" : [
{
"orientation" : "portrait",
"idiom" : "iphone",
"filename" : "launch-portrait-iphone@x2.png",
"extent" : "full-screen",
"minimum-system-version" : "7.0",
"scale" : "2x"
},
{
"extent" : "full-screen",
"idiom" : "iphone",
"subtype" : "retina4",
"filename" : "launch-portrait-iphone@r4.png",
"minimum-system-version" : "7.0",
"orientation" : "portrait",
"scale" : "2x"
},
{
"orientation" : "portrait",
"idiom" : "ipad",
"filename" : "launch-portrait@x1.png",
"extent" : "full-screen",
"minimum-system-version" : "7.0",
"scale" : "1x"
},
{
"orientation" : "landscape",
"idiom" : "ipad",
"filename" : "launch-landscape@x1.png",
"extent" : "full-screen",
"minimum-system-version" : "7.0",
"scale" : "1x"
},
{
"orientation" : "portrait",
"idiom" : "ipad",
"filename" : "launch-portrait@2x.png",
"extent" : "full-screen",
"minimum-system-version" : "7.0",
"scale" : "2x"
},
{
"orientation" : "landscape",
"idiom" : "ipad",
"filename" : "launch-landscape@x2.png",
"extent" : "full-screen",
"minimum-system-version" : "7.0",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2017-2020 The Khronos Group Inc.
SPDX-License-Identifier: Apache-2.0
-->
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22685"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
<viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" textAlignment="center" lineBreakMode="middleTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="GJd-Yh-RWb">
<rect key="frame" x="20" y="300" width="560" height="88"/>
<string key="text">KTX Texture
OpenGL ${version} Loader Tests</string>
<fontDescription key="fontDescription" name="HelveticaNeue" family="Helvetica Neue" pointSize="36"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<color key="shadowColor" red="0.66666668653488159" green="0.66666668653488159" blue="0.66666668653488159" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<size key="shadowOffset" width="1" height="2"/>
</label>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="GJd-Yh-RWb" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leadingMargin" id="7go-Ai-m2p"/>
<constraint firstItem="GJd-Yh-RWb" firstAttribute="trailing" secondItem="Ze5-6b-2t3" secondAttribute="trailingMargin" id="G9l-Nb-7mW"/>
<constraint firstItem="GJd-Yh-RWb" firstAttribute="top" secondItem="Llm-lL-Icb" secondAttribute="bottom" constant="162" id="PSH-VH-8d0"/>
<constraint firstItem="xb3-aO-Qok" firstAttribute="top" secondItem="GJd-Yh-RWb" secondAttribute="bottom" constant="325" id="U3h-5B-haO"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>
@@ -0,0 +1,30 @@
# Copyright 2023 Khronos Group, Inc.
# SPDX-License-Identifier: Apache-2.0
# If this file's extension is .desktop DO NOT edit it. It is generated from
# glloadtests.desktop.in.
#
# If the extension is .in feel free to edit it. After editing you MUST
# run cmake project configuration again to generate updated output files.
[Desktop Entry]
Version=1.0
Name=${version} Load Tests
Name[ja]=${version} Load Tests
Name[fr]=${version} Load Tests
GenericName=KTX loader tests and file viewer
GenericName[ja]=KTXローダーテストとファイルビューア
GenericName[fr]=Tests de chargeur KTX et visionneuse de fichiers
Comment=View KTX files using OpenGL or run GLUpload functionality tests.
Comment[fr]=Affichez les fichiers KTX à l'aide d'OpenGL ou exécutez des tests de fonctionnalité GLUpload.
Comment[ja]=OpenGLを使用してKTXファイルを表示するか、GLUpload機能テストを実行します。
TryExec=${target}
Exec=${target} %F
Terminal=false
Type=Application
Keywords=Ktx;Texture;Viewer
Keywords[ja]=Ktx;Texture;Viewer
Keywords[fr]=Ktx;Texture;Viewer
Icon=${TARGET_INSTALL_RESOURCES_FULL_DIR}/ktx_app.svg
Categories=Utility;
StartupNotify=true
MimeType=image/ktx;image/ktx2;
@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2015-2024 Mark Callow
SPDX-License-Identifier: Apache-2.0
WARNING: Do not attempt to edit the Info.plist you see in the Xcode
project. It is unavoidably configured from this by CMake. Edit here.
-->
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
<key>CFBundleDisplayName</key>
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
<key>CFBundleGetInfoString</key>
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
<key>NSHumanReadableCopyright</key>
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
<key>CFBundleIdentifier</key>
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleVersion</key>
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
<key>CFBundleShortVersionString</key>
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>LSIsAppleDefaultForType</key>
<false/>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>CFBundleTypeIconFile</key>
<string>${KTX_DOC_ICON}</string>
<key>LSItemContentTypes</key>
<array>
<string>public.ktx2</string>
<string>public.ktx</string>
</array>
<key>CFBundleTypeExtensions</key>
<array>
<string>ktx</string>
<string>ktx2</string>
</array>
<key>CFBundleTypeMIMETypes</key>
<array>
<string>image/ktx</string>
<string>image/ktx2</string>
</array>
<key>CFBundleTypeName</key>
<string>KTX texture file</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSTypeIsPackage</key>
<false/>
</dict>
</array>
<key>LSApplicationCategoryType</key>
<string>public.app-category.graphics-design</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>NSMainNibFile</key>
<string></string>
<key>UISupportedInterfaceOrientations</key>
<array/>
</dict>
</plist>
@@ -0,0 +1,424 @@
/* -*- tab-iWidth: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2018-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief Tests texture loading by using glDrawTexture to draw the texture.
*
* @author Mark Callow
*/
#if defined(_WIN32)
#if _MSC_VER < 1900
#define snprintf _snprintf
#endif
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <assert.h>
#include <sstream>
#include <ktx.h>
#include "disable_glm_warnings.h"
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include "reenable_warnings.h"
#include "DrawTexture.h"
#include "GLTextureTranscoder.hpp"
#include "TranscodeTargetStrToFmt.h"
#include "frame.h"
#include "quad.h"
#include "argparser.h"
#include "ltexceptions.h"
#if !defined(GL_TEXTURE_1D)
#define GL_TEXTURE_1D 0x0DE0
#endif
/* ------------------------------------------------------------------------- */
extern const GLchar* pszVs;
extern const GLchar *pszDecalFs, *pszDecalSrgbEncodeFs;
extern const GLchar *pszColorFs, *pszColorSrgbEncodeFs;
/* ------------------------------------------------------------------------- */
LoadTestSample*
DrawTexture::create(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath)
{
return new DrawTexture(width, height, szArgs, sBasePath);
}
DrawTexture::DrawTexture(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath)
: GL3LoadTestSample(width, height, szArgs, sBasePath)
{
GLfloat* pfQuadTexCoords = quad_texture;
GLfloat fTmpTexCoords[sizeof(quad_texture)/sizeof(GLfloat)];
GLenum target;
GLenum glerror;
GLint sign_s = 1, sign_t = 1;
GLint i;
GLuint gnColorFs, gnDecalFs, gnVs;
GLsizeiptr offset;
ktxTexture* kTexture;
KTX_error_code ktxresult;
bInitialized = false;
transcodeTarget = KTX_TTF_NOSELECTION;
gnTexture = 0;
processArgs(szArgs);
std::string ktxfilepath = externalFile ? ktxfilename
: getAssetPath() + ktxfilename;
ktxresult = ktxTexture_CreateFromNamedFile(ktxfilepath.c_str(),
preloadImages ? KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT
: KTX_TEXTURE_CREATE_NO_FLAGS,
&kTexture);
if (KTX_SUCCESS != ktxresult) {
std::stringstream message;
message << "Creation of ktxTexture from \"" << ktxfilepath
<< "\" failed: " << ktxErrorString(ktxresult);
throw std::runtime_error(message.str());
}
if (ktxTexture_NeedsTranscoding(kTexture)) {
TextureTranscoder tc;
tc.transcode((ktxTexture2*)kTexture, transcodeTarget);
}
ktxresult = ktxTexture_GLUpload(kTexture, &gnTexture, &target, &glerror);
if (KTX_SUCCESS == ktxresult) {
// GLUpload won't set target to GL_TEXTURE_1D not supported by context.
if (target != GL_TEXTURE_1D && target != GL_TEXTURE_2D) {
/* Can only draw 1D & 2D textures */
std::stringstream message;
glDeleteTextures(1, &gnTexture);
message << "DrawTexture supports only 1D & 2D textures. \""
<< ktxfilename << "\" is not one of these.";
throw std::runtime_error(message.str());
}
if (kTexture->orientation.x == KTX_ORIENT_X_LEFT)
sign_s = -1;
if (kTexture->orientation.y == KTX_ORIENT_Y_DOWN)
sign_t = -1;
if (sign_s < 0 || sign_t < 0) {
// Transform the texture coordinates to get correct image
// orientation.
int iNumCoords = sizeof(quad_texture) / sizeof(float);
for (i = 0; i < iNumCoords; i++) {
fTmpTexCoords[i] = quad_texture[i];
if (i & 1) { // odd, i.e. a y coordinate
if (sign_t < 1) {
fTmpTexCoords[i] = fTmpTexCoords[i] * -1 + 1;
}
} else { // an x coordinate
if (sign_s < 1) {
fTmpTexCoords[i] = fTmpTexCoords[i] * -1 + 1;
}
}
}
pfQuadTexCoords = fTmpTexCoords;
}
uTexWidth = kTexture->baseWidth;
uTexHeight = kTexture->baseHeight;
if (kTexture->numLevels > 1)
// Enable bilinear mipmapping.
// TO DO: application can consider inserting a key,value pair in
// the KTX file that indicates what type of filtering to use.
glTexParameteri(target,
GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
else
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
ktx_uint32_t swizzleLen;
char* swizzleStr;
ktxresult = ktxHashList_FindValue(&kTexture->kvDataHead, KTX_SWIZZLE_KEY,
&swizzleLen, (void**)&swizzleStr);
if (ktxresult == KTX_SUCCESS && swizzleLen == 5) {
if (contextSupportsSwizzle()) {
GLint swizzle[4];
swizzle[0] = swizzleStr[0] == 'r' ? GL_RED
: swizzleStr[0] == 'g' ? GL_GREEN
: swizzleStr[0] == 'b' ? GL_BLUE
: swizzleStr[0] == 'a' ? GL_ALPHA
: swizzleStr[0] == '0' ? GL_ZERO
: GL_ONE;
swizzle[1] = swizzleStr[1] == 'r' ? GL_RED
: swizzleStr[1] == 'g' ? GL_GREEN
: swizzleStr[1] == 'b' ? GL_BLUE
: swizzleStr[1] == 'a' ? GL_ALPHA
: swizzleStr[1] == '0' ? GL_ZERO
: GL_ONE;
swizzle[2] = swizzleStr[2] == 'r' ? GL_RED
: swizzleStr[2] == 'g' ? GL_GREEN
: swizzleStr[2] == 'b' ? GL_BLUE
: swizzleStr[2] == 'a' ? GL_ALPHA
: swizzleStr[2] == '2' ? GL_ZERO
: GL_ONE;
swizzle[3] = swizzleStr[3] == 'r' ? GL_RED
: swizzleStr[3] == 'g' ? GL_GREEN
: swizzleStr[3] == 'b' ? GL_BLUE
: swizzleStr[3] == 'a' ? GL_ALPHA
: swizzleStr[3] == '3' ? GL_ZERO
: GL_ONE;
glTexParameteri(target, GL_TEXTURE_SWIZZLE_R, swizzle[0]);
glTexParameteri(target, GL_TEXTURE_SWIZZLE_G, swizzle[1]);
glTexParameteri(target, GL_TEXTURE_SWIZZLE_B, swizzle[2]);
glTexParameteri(target, GL_TEXTURE_SWIZZLE_A, swizzle[3]);
} else {
std::stringstream message;
message << "Input file has swizzle metadata but the "
<< "GL context does not support swizzling.";
// I have absolutely no idea why the following line makes clang
// raise an error about no matching conversion from
// std::__1::basic_stringstream to unsupported_ttype
// so I've resorted to using a temporary variable.
//throw(unsupported_ttype(message.str());
std::string msg = message.str();
throw(unsupported_ttype(msg));
}
}
assert(GL_NO_ERROR == glGetError());
ktxTexture_Destroy(kTexture);
} else {
std::stringstream message;
message << "ktxTexture_GLUpload failed: ";
if (ktxresult != KTX_GL_ERROR) {
message << ktxErrorString(ktxresult);
throw std::runtime_error(message.str());
} else if (kTexture->isCompressed
// Emscripten/WebGL returns INVALID_VALUE for unsupported
// ETC formats.
&& (glerror == GL_INVALID_ENUM || glerror == GL_INVALID_VALUE)) {
throw unsupported_ctype();
} else {
message << std::showbase << "GL error " << std::hex << glerror
<< " occurred.";
throw std::runtime_error(message.str());
}
}
glClearColor(0.4f, 0.4f, 0.5f, 1.0f);
// Must have vertex data in buffer objects to use VAO's on ES3/GL Core
glGenBuffers(1, &gnVbo);
glBindBuffer(GL_ARRAY_BUFFER, gnVbo);
// Create the buffer data store
glBufferData(GL_ARRAY_BUFFER,
sizeof(frame_position) + sizeof(frame_color)
+ sizeof(quad_position) + sizeof(quad_color)
+ sizeof(quad_texture),
NULL, GL_STATIC_DRAW);
glGenVertexArrays(2, gnVaos);
// Interleave data copying and attrib pointer setup so offset is only
// computed once.
// Setup VAO and buffer the data for frame
glBindVertexArray(gnVaos[FRAME]);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
offset = 0;
glBufferSubData(GL_ARRAY_BUFFER, offset,
sizeof(frame_position), frame_position);
glVertexAttribPointer(0, 3, GL_BYTE, GL_FALSE, 0, (GLvoid*)offset);
offset += sizeof(frame_position);
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(frame_color), frame_color);
glVertexAttribPointer(1, 3, GL_BYTE, GL_FALSE, 0, (GLvoid*)offset);
offset += sizeof(frame_color);
// Setup VAO for quad
glBindVertexArray(gnVaos[QUAD]);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glBufferSubData(GL_ARRAY_BUFFER, offset,
sizeof(quad_position), quad_position);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid*)offset);
offset += sizeof(quad_position);
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(quad_color), quad_color);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid*)offset);
offset += sizeof(quad_color);
glBufferSubData(GL_ARRAY_BUFFER, offset,
sizeof(quad_texture), pfQuadTexCoords);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid*)offset);
glBindVertexArray(0);
const GLchar *actualColorFs, *actualDecalFs;
if (framebufferColorEncoding() == GL_LINEAR) {
actualColorFs = pszColorSrgbEncodeFs;
actualDecalFs = pszDecalSrgbEncodeFs;
} else {
actualColorFs = pszColorFs;
actualDecalFs = pszDecalFs;
}
try {
makeShader(GL_VERTEX_SHADER, pszVs, &gnVs);
makeShader(GL_FRAGMENT_SHADER, actualColorFs, &gnColorFs);
makeProgram(gnVs, gnColorFs, &gnColProg);
gulMvMatrixLocCP = glGetUniformLocation(gnColProg, "mvmatrix");
gulPMatrixLocCP = glGetUniformLocation(gnColProg, "pmatrix");
makeShader(GL_FRAGMENT_SHADER, actualDecalFs, &gnDecalFs);
makeProgram(gnVs, gnDecalFs, &gnTexProg);
} catch (std::exception& e) {
(void)e; // To quiet unused variable warnings from some compilers.
throw;
}
gulMvMatrixLocTP = glGetUniformLocation(gnTexProg, "mvmatrix");
gulPMatrixLocTP = glGetUniformLocation(gnTexProg, "pmatrix");
gulSamplerLocTP = glGetUniformLocation(gnTexProg, "sampler");
glUseProgram(gnTexProg);
// We're using the default texture unit 0
glUniform1i(gulSamplerLocTP, 0);
glDeleteShader(gnVs);
glDeleteShader(gnColorFs);
glDeleteShader(gnDecalFs);
// Set the quad's mv matrix to scale by the texture size.
// With the pixel-mapping ortho projection set below, the texture will
// be rendered at actual size just like DrawTex*OES.
quadMvMatrix = glm::scale(glm::mat4(),
glm::vec3((float)uTexWidth / 2,
(float)uTexHeight / 2,
1));
assert(GL_NO_ERROR == glGetError());
bInitialized = true;
}
/* ------------------------------------------------------------------------- */
DrawTexture::~DrawTexture()
{
if (bInitialized) {
// A bug in the PVR SDK 3.1 emulator causes the
// glDeleteProgram(gnColProg) below to raise an INVALID_VALUE error
// if the following glUseProgram(0) has been executed. Strangely the
// equivalent line in TexturedCube.cpp, where only 1 program is used,
// does not raise an error.
glUseProgram(0);
glDeleteTextures(1, &gnTexture);
glDeleteProgram(gnTexProg);
glDeleteProgram(gnColProg);
glDeleteBuffers(1, &gnVbo);
glDeleteVertexArrays(2, gnVaos);
}
assert(GL_NO_ERROR == glGetError());
}
/* ------------------------------------------------------------------------- */
void
DrawTexture::processArgs(std::string sArgs)
{
// Options descriptor
struct argparser::option longopts[] = {
{"external", argparser::option::no_argument, &externalFile, 1},
{"preload", argparser::option::no_argument, &preloadImages, 1},
{"transcode-target", argparser::option::required_argument, nullptr, 2},
{NULL, argparser::option::no_argument, nullptr, 0}
};
argvector argv(sArgs);
argparser ap(argv);
int ch;
while ((ch = ap.getopt(nullptr, longopts, nullptr)) != -1) {
switch (ch) {
case 0: break;
case 2:
transcodeTarget = TranscodeTargetStrToFmt(ap.optarg);
break;
default: assert(false); // Error in args in sample table.
}
}
assert(ap.optind < argv.size());
ktxfilename = argv[ap.optind];
}
/* ------------------------------------------------------------------------- */
void
DrawTexture::resize(uint32_t uNewWidth, uint32_t uNewHeight)
{
glViewport(0, 0, uNewWidth, uNewHeight);
this->uWidth = uNewWidth;
this->uHeight = uNewHeight;
// Set up an orthographic projection where 1 = 1 pixel, and 0,0,0
// is at the center of the window.
//atSetOrthoZeroAtCenterMatrix(fPMatrix,
// 0.0f, (float)iWidth,
// 0.0f, (float)iHeight,
// -1.0f, 1.0f);
// Set up an orthographic projection where 1 = 1 pixel
pMatrix = glm::ortho(0.f, (float)uWidth, 0.f, (float)uHeight);
// Move (0,0,0) to the center of the window.
pMatrix *= glm::translate(glm::mat4(),
glm::vec3((float)uWidth/2, (float)uHeight/2, 0));
// Scale the frame to fill the viewport. To guarantee its lines
// appear we need to inset them by half-a-pixel hence the -1.
// [Lines at the edges of the clip volume may or may not appear
// depending on the OpenGL ES implementation. This is because
// (a) the edges are on the points of the diamonds of the diamond
// exit rule and slight precision errors can easily push the
// lines outside the diamonds.
// (b) the specification allows lines to be up to 1 pixel either
// side of the exact position.]
frameMvMatrix = glm::scale(glm::mat4(),
glm::vec3((float)(uWidth - 1)/2,
(float)(uHeight - 1)/2,
1));
}
/* ------------------------------------------------------------------------- */
void
DrawTexture::run(uint32_t /*msTicks*/)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glBindVertexArray(gnVaos[FRAME]);
glUseProgram(gnColProg);
glUniformMatrix4fv(gulMvMatrixLocCP, 1, GL_FALSE,
glm::value_ptr(frameMvMatrix));
glUniformMatrix4fv(gulPMatrixLocCP, 1, GL_FALSE, glm::value_ptr(pMatrix));
glDrawArrays(GL_LINE_LOOP, 0, 4);
glBindVertexArray(gnVaos[QUAD]);
glUseProgram(gnTexProg);
glUniformMatrix4fv(gulMvMatrixLocTP, 1, GL_FALSE,
glm::value_ptr(quadMvMatrix));
glUniformMatrix4fv(gulPMatrixLocTP, 1, GL_FALSE, glm::value_ptr(pMatrix));
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
assert(GL_NO_ERROR == glGetError());
}
/* ------------------------------------------------------------------------- */
@@ -0,0 +1,71 @@
/* -*- tab-iWidth: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2018-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief Definition of texture loading test using glDrawTexture.
*
* @author Mark Callow
*/
#ifndef DRAW_TEXTURE_H
#define DRAW_TEXTURE_H
#include "GL3LoadTestSample.h"
class DrawTexture : public GL3LoadTestSample {
public:
DrawTexture(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath);
~DrawTexture();
virtual void resize(uint32_t width, uint32_t height);
virtual void run(uint32_t msTicks);
//virtual void getOverlayText(GLTextOverlay *textOverlay);
static LoadTestSample*
create(uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath);
protected:
void processArgs(std::string sArgs);
int preloadImages = 0;
uint32_t uWidth;
uint32_t uHeight;
uint32_t uTexWidth;
uint32_t uTexHeight;
glm::mat4 frameMvMatrix;
glm::mat4 quadMvMatrix;
glm::mat4 pMatrix;
GLuint gnTexture;
GLuint gnTexProg;
GLuint gnColProg;
#define FRAME 0
#define QUAD 1
GLuint gnVaos[2];
GLuint gnVbo;
GLint gulMvMatrixLocTP;
GLint gulPMatrixLocTP;
GLint gulSamplerLocTP;
GLint gulMvMatrixLocCP;
GLint gulPMatrixLocCP;
bool bInitialized;
ktx_transcode_fmt_e transcodeTarget;
};
#endif /* DRAW_TEXTURE_H */
@@ -0,0 +1,326 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 sts expandtab: */
/*
* Copyright 2018-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief Encode a texture then texture a cube with it, transcoding if necessary.
*
* This is used principally to check the encoders are properly linked on platforms where the ktx tools are
* unavailable and libktx is a static library.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#if defined(_WIN32)
#if _MSC_VER < 1900
#define snprintf _snprintf
#endif
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <assert.h>
#include <sstream>
#include <ktx.h>
#include "disable_glm_warnings.h"
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include "reenable_warnings.h"
#include "EncodeTexture.h"
#include "GLTextureTranscoder.hpp"
#include "TranscodeTargetStrToFmt.h"
#include "cube.h"
#include "argparser.h"
#include "ltexceptions.h"
/* ------------------------------------------------------------------------- */
extern const GLchar* pszVs;
extern const GLchar *pszDecalFs, *pszDecalSrgbEncodeFs;
/* ------------------------------------------------------------------------- */
LoadTestSample*
EncodeTexture::create(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath)
{
return new EncodeTexture(width, height, szArgs, sBasePath);
}
EncodeTexture::EncodeTexture(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath)
: GL3LoadTestSample(width, height, szArgs, sBasePath)
{
std::string filename;
GLenum target;
GLenum glerror;
GLuint gnDecalFs, gnVs;
GLsizeiptr offset;
ktxTexture2* kTexture;
KTX_error_code ktxresult;
bInitialized = GL_FALSE;
gnTexture = 0;
encodeTarget = EF_ETC1S;
transcodeTarget = KTX_TTF_NOSELECTION;
processArgs(szArgs);
filename = getAssetPath() + ktxfilename;
ktxresult = ktxTexture_CreateFromNamedFile(filename.c_str(),
KTX_TEXTURE_CREATE_NO_FLAGS,
(ktxTexture**)&kTexture);
if (KTX_SUCCESS != ktxresult) {
std::stringstream message;
message << "Creation of ktxTexture from \"" << filename
<< "\" failed: " << ktxErrorString(ktxresult);
throw std::runtime_error(message.str());
}
if (!kTexture->isCompressed) {
switch (encodeTarget) {
case EF_ASTC:
ktxresult = ktxTexture2_CompressAstc(kTexture, 0);
break;
case EF_ETC1S:
ktxresult = ktxTexture2_CompressBasis(kTexture, 0);
break;
case EF_UASTC:
{
ktxBasisParams params = { };
params.structSize = sizeof(params);
params.uastc = KTX_TRUE;
params.threadCount = 1;
ktxresult = ktxTexture2_CompressBasisEx(kTexture, &params);
break;
}
}
if (KTX_SUCCESS != ktxresult) {
std::stringstream message;
message << "Encoding of ktxTexture2 to " << encodeTarget
<< " failed: " << ktxErrorString(ktxresult);
throw std::runtime_error(message.str());
}
}
if (ktxTexture2_NeedsTranscoding(kTexture)) {
TextureTranscoder tc;
tc.transcode((ktxTexture2*)kTexture);
}
ktxresult = ktxTexture_GLUpload(ktxTexture(kTexture), &gnTexture, &target,
&glerror);
if (KTX_SUCCESS == ktxresult) {
if (target != GL_TEXTURE_2D) {
/* Can only draw 2D textures */
std::stringstream message;
glDeleteTextures(1, &gnTexture);
message << "App can only draw 2D textures.";
throw std::runtime_error(message.str());
}
if (kTexture->numLevels > 1)
// Enable bilinear mipmapping.
// TO DO: application can consider inserting a key,value pair in
// the KTX file that indicates what type of filtering to use.
glTexParameteri(target,
GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
else
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
ktxTexture_Destroy(ktxTexture(kTexture));
assert(GL_NO_ERROR == glGetError());
} else {
std::stringstream message;
message << "Load of texture from \"" << filename << "\" failed: ";
if (ktxresult == KTX_GL_ERROR && glerror == GL_INVALID_ENUM) {
throw unsupported_ctype();
} else {
if (ktxresult == KTX_GL_ERROR) {
message << std::showbase << "GL error " << std::hex << glerror
<< " occurred.";
} else {
message << ktxErrorString(ktxresult);
}
throw std::runtime_error(message.str());
}
}
// By default dithering is enabled. Dithering does not provide visual
// improvement in this sample so disable it to improve performance.
glDisable(GL_DITHER);
glEnable(GL_CULL_FACE);
glClearColor(0.2f,0.3f,0.4f,1.0f);
// Create a VAO and bind it.
glGenVertexArrays(1, &gnVao);
glBindVertexArray(gnVao);
// Must have vertex data in buffer objects to use VAO's on ES3/GL Core
glGenBuffers(2, gnVbo);
glBindBuffer(GL_ARRAY_BUFFER, gnVbo[0]);
// Must be done after the VAO is bound
// WebGL requires different buffers for data and indices.
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gnVbo[1]);
// Create the buffer data store.
glBufferData(GL_ARRAY_BUFFER,
sizeof(cube_face) + sizeof(cube_color) + sizeof(cube_texture)
+ sizeof(cube_normal), NULL, GL_STATIC_DRAW);
// Interleave data copying and attrib pointer setup so offset is only
// computed once.
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glEnableVertexAttribArray(3);
offset = 0;
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(cube_face), cube_face);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid*)offset);
offset += sizeof(cube_face);
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(cube_color), cube_color);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid*)offset);
offset += sizeof(cube_color);
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(cube_texture), cube_texture);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid*)offset);
offset += sizeof(cube_texture);
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(cube_normal), cube_normal);
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid*)offset);
offset += sizeof(cube_normal);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(cube_index_buffer),
cube_index_buffer, GL_STATIC_DRAW);
const GLchar* actualDecalFs;
if (framebufferColorEncoding() == GL_LINEAR) {
actualDecalFs = pszDecalSrgbEncodeFs;
} else {
actualDecalFs = pszDecalFs;
}
try {
makeShader(GL_VERTEX_SHADER, pszVs, &gnVs);
makeShader(GL_FRAGMENT_SHADER, actualDecalFs, &gnDecalFs);
makeProgram(gnVs, gnDecalFs, &gnTexProg);
} catch (std::exception& e) {
(void)e; // To quiet unused variable warnings from some compilers.
throw;
}
gulMvMatrixLocTP = glGetUniformLocation(gnTexProg, "mvmatrix");
gulPMatrixLocTP = glGetUniformLocation(gnTexProg, "pmatrix");
gulSamplerLocTP = glGetUniformLocation(gnTexProg, "sampler");
glUseProgram(gnTexProg);
// We're using the default texture unit 0
glUniform1i(gulSamplerLocTP, 0);
glDeleteShader(gnVs);
glDeleteShader(gnDecalFs);
assert(GL_NO_ERROR == glGetError());
bInitialized = GL_TRUE;
}
EncodeTexture::~EncodeTexture()
{
glEnable(GL_DITHER);
glDisable(GL_CULL_FACE);
if (bInitialized) {
glUseProgram(0);
glDeleteTextures(1, &gnTexture);
glDeleteProgram(gnTexProg);
glDeleteBuffers(2, gnVbo);
glDeleteVertexArrays(1, &gnVao);
}
assert(GL_NO_ERROR == glGetError());
}
/* ------------------------------------------------------------------------- */
void
EncodeTexture::processArgs(std::string sArgs)
{
// Options descriptor
struct argparser::option longopts[] = {
{"encode", argparser::option::required_argument, nullptr, 1},
{"transcode-target", argparser::option::required_argument, nullptr, 2},
{NULL, argparser::option::no_argument, nullptr, 0}
};
argvector argv(sArgs);
argparser ap(argv);
int ch;
while ((ch = ap.getopt(nullptr, longopts, nullptr)) != -1) {
switch (ch) {
case 0: break;
case 1:
if (!ap.optarg.compare("astc"))
encodeTarget = EF_ASTC;
else if (!ap.optarg.compare("etc1s"))
encodeTarget = EF_ETC1S;
else if (!ap.optarg.compare("uastc"))
encodeTarget = EF_UASTC;
else
assert(false && "Error in args in sample table");
break;
case 2:
transcodeTarget = TranscodeTargetStrToFmt(ap.optarg);
break;
default: assert(false && "Error in args in sample table");
}
}
assert(ap.optind < argv.size());
ktxfilename = argv[ap.optind];
}
/* ------------------------------------------------------------------------- */
void
EncodeTexture::resize(uint32_t uWidth, uint32_t uHeight)
{
glm::mat4 matProj;
glViewport(0, 0, uWidth, uHeight);
matProj = glm::perspective(glm::radians(45.f),
uWidth / (float)uHeight,
1.f, 100.f);
glUniformMatrix4fv(gulPMatrixLocTP, 1, GL_FALSE, glm::value_ptr(matProj));
}
void
EncodeTexture::run(uint32_t msTicks)
{
// Setup the view matrix : just turn around the cube.
const float fDistance = 5.0f;
glm::vec3 eye((float)cos( msTicks*0.001f ) * fDistance,
(float)sin( msTicks*0.0007f ) * fDistance,
(float)sin( msTicks*0.001f ) * fDistance);
glm::vec3 look(0.,0.,0.);
glm::vec3 up(0.,1.,0.);
glm::mat4 matView = glm::lookAt( eye, look, up );
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUniformMatrix4fv(gulMvMatrixLocTP, 1, GL_FALSE, glm::value_ptr(matView));
glDrawElements(GL_TRIANGLES, CUBE_NUM_INDICES, GL_UNSIGNED_SHORT, 0);
assert(GL_NO_ERROR == glGetError());
}
/* ------------------------------------------------------------------------- */
@@ -0,0 +1,74 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2018-2022 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief GLLoadTestSample derived class for encoding a texture and texturing a cube with it.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#ifndef ENCODE_TEXTURE_H
#define ENCODE_TEXTURE_H
#include "GL3LoadTestSample.h"
#include <iostream>
class EncodeTexture : public GL3LoadTestSample {
public:
EncodeTexture(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath);
~EncodeTexture();
virtual void resize(uint32_t width, uint32_t height);
virtual void run(uint32_t msTicks);
//virtual void getOverlayText(GLTextOverlay *textOverlay);
static LoadTestSample*
create(uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath);
protected:
void processArgs(std::string sArgs);
GLuint gnTexture;
GLuint gnTexProg;
GLuint gnVao;
GLuint gnVbo[2];
GLint gulMvMatrixLocTP;
GLint gulPMatrixLocTP;
GLint gulSamplerLocTP;
bool bInitialized;
ktx_transcode_fmt_e transcodeTarget;
enum encode_fmt_e { EF_ASTC = 1, EF_ETC1S = 2, EF_UASTC = 3 };
friend std::ostream& operator<<(std::ostream& os, encode_fmt_e format);
encode_fmt_e encodeTarget;
};
inline std::ostream& operator<<(std::ostream& os, EncodeTexture::encode_fmt_e format)
{
switch (format) {
case EncodeTexture::EF_ASTC:
os << "astc";
break;
case EncodeTexture::EF_ETC1S:
os << "etc1s";
break;
case EncodeTexture::EF_UASTC:
os << "uastc";
break;
}
return os;
}
#endif /* ENCODE_TEXTURE_H */
@@ -0,0 +1,281 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2017-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @class GLLoadTestSample
* @~English
*
* @brief Definition of a base class for OpenGL texture loading test samples.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#include <string.h>
#include <sstream>
#include <ktx.h>
#include <SDL3/SDL_platform.h>
#include "GL3LoadTestSample.h"
/* ------------------------------------------------------------------------- */
static const GLchar* pszESLangVer = "#version 300 es\n";
// location layout qualifier did not appear until version 330.
static const GLchar* pszGLLangVer = "#version 330 core\n";
/* ------------------------------------------------------------------------- */
void
GL3LoadTestSample::makeShader(GLenum type,
ShaderSource& source,
GLuint* shader)
{
GLint sh = glCreateShader(type);
GLint shaderCompiled;
if (strstr((const char*)glGetString(GL_VERSION), "GL ES") == NULL)
source.insert(source.cbegin(), pszGLLangVer);
else
source.insert(source.cbegin(), pszESLangVer);
glShaderSource(sh, (GLsizei)source.size(), source.data(), NULL);
glCompileShader(sh);
// Check if compilation succeeded
glGetShaderiv(sh, GL_COMPILE_STATUS, &shaderCompiled);
if (!shaderCompiled) {
// An error happened, first retrieve the length of the log message
int logLength, charsWritten;
char* infoLog;
std::stringstream message;
glGetShaderiv(sh, GL_INFO_LOG_LENGTH, &logLength);
// Allocate enough space for the message and retrieve it
infoLog = new char[logLength];
glGetShaderInfoLog(sh, logLength, &charsWritten, infoLog);
message << "makeShader compilation error" << std::endl << infoLog;
delete[] infoLog;
glDeleteShader(sh);
throw std::runtime_error(message.str());
} else {
*shader = sh;
}
}
void
GL3LoadTestSample::makeShader(GLenum type, const GLchar* const source,
GLuint* shader)
{
ShaderSource ss;
ss.push_back(source);
makeShader(type, ss, shader);
}
void
GL3LoadTestSample::makeProgram(GLuint vs, GLuint fs, GLuint* program)
{
GLint linked;
GLint fsCompiled, vsCompiled;
glGetShaderiv(vs, GL_COMPILE_STATUS, &fsCompiled);
glGetShaderiv(fs, GL_COMPILE_STATUS, &vsCompiled);
if (fsCompiled && vsCompiled) {
GLuint prog = glCreateProgram();
glAttachShader(prog, vs);
glAttachShader(prog, fs);
glLinkProgram(prog);
// Check if linking succeeded in the same way we checked for compilation success
glGetProgramiv(prog, GL_LINK_STATUS, &linked);
if (!linked) {
int logLength, charsWritten;
char* infoLog;
std::stringstream message;
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
infoLog = new char[logLength];
glGetProgramInfoLog(prog, logLength, &charsWritten, infoLog);
message << "makeProgram link error" << std::endl << infoLog;
delete[] infoLog;
glDeleteProgram(prog);
throw std::runtime_error(message.str());
}
*program = prog;
} else {
std::stringstream message;
message << "makeProgram: either vertex or fragment shader is not compiled.";
throw std::runtime_error(message.str());
}
}
#if !defined(GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT)
#define GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT 0x8A54
#define GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG 0x8C01
#define GL_COMPRESSED_RGBA_PVRTC_2BPPV2_IMG 0x9137
#endif
#if !defined(GL_COMPRESSED_RG_RGTC2)
#define GL_COMPRESSED_RG_RGTC2 0x8DBD
#endif
#if !defined(GL_COMPRESSED_RGBA_BPTC_UNORM)
#define GL_COMPRESSED_RGBA_BPTC_UNORM 0x8E8C
#endif
#if !defined(GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT)
#define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT 0x8E8E
#endif
void
GL3LoadTestSample::determineCompressedTexFeatures(compressedTexFeatures& features)
{
ktx_int32_t numCompressedFormats;
memset(&features, false, sizeof(features));
glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &numCompressedFormats);
GLint* formats = new GLint[numCompressedFormats];
glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, formats);
for (ktx_int32_t i = 0; i < numCompressedFormats; i++) {
if (formats[i] == GL_COMPRESSED_RGBA8_ETC2_EAC)
features.etc2 = true;
if (formats[i] == GL_ETC1_RGB8_OES)
features.etc1 = true;
if (formats[i] == GL_COMPRESSED_RGBA_S3TC_DXT5_EXT)
features.bc3 = true;
if (formats[i] == GL_COMPRESSED_RG_RGTC2)
features.rgtc = true;
if (formats[i] == GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT)
features.pvrtc_srgb = true;
if (formats[i] == GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG)
features.pvrtc1 = true;
if (formats[i] == GL_COMPRESSED_RGBA_PVRTC_2BPPV2_IMG)
features.pvrtc2 = true;
if (formats[i] == GL_COMPRESSED_RGBA_ASTC_4x4_KHR)
features.astc_ldr = true;
if (formats[i] == GL_COMPRESSED_RGBA_BPTC_UNORM)
features.bc7 = true;
if (formats[i] == GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT)
features.bc6h = true;
}
delete[] formats;
// Just in case COMPRESSED_TEXTURE_FORMATS didn't return anything.
// There is no ETC2 extension. It went into core in OpenGL ES 2.0.
// ARB_es_compatibility is not a good indicator. ETC2 could be supported
// by software decompression. Better to report unsupported.
if (!features.etc1 && SDL_GL_ExtensionSupported("GL_OES_compressed_ETC1_RGB8_texture"))
features.etc1 = true;;
if (!features.bc3 && SDL_GL_ExtensionSupported("GL_EXT_texture_compression_s3tc"))
features.bc3 = true;
if (!features.rgtc && SDL_GL_ExtensionSupported("GL_ARB_texture_compression_rgtc"))
features.rgtc = true;
if (!features.pvrtc1 && SDL_GL_ExtensionSupported("GL_IMG_texture_compression_pvrtc"))
features.pvrtc1 = true;
if (!features.pvrtc2 && SDL_GL_ExtensionSupported("GL_IMG_texture_compression_pvrtc2"))
features.pvrtc2 = true;
if (!features.pvrtc_srgb && SDL_GL_ExtensionSupported("GL_EXT_pvrtc_sRGB"))
features.pvrtc_srgb = true;
if (!(features.bc7 && features.bc6h) && SDL_GL_ExtensionSupported("GL_ARB_texture_compression_bptc"))
features.bc6h = features.bc7 = true;
if (!features.astc_ldr && SDL_GL_ExtensionSupported("GL_KHR_texture_compression_astc_ldr"))
features.astc_ldr = true;
// The only way to identify this support is the extension string.
// The format name is the same.
if (SDL_GL_ExtensionSupported("GL_KHR_texture_compression_astc_hdr"))
features.astc_hdr = true;
}
bool
GL3LoadTestSample::contextSupportsSwizzle()
{
bool esProfile = false;
GLint majorVersion, minorVersion;
if (strstr((const char*)glGetString(GL_VERSION), "GL ES") != NULL) {
esProfile = true;
}
// MAJOR & MINOR only introduced in GL {,ES} 3.0
glGetIntegerv(GL_MAJOR_VERSION, &majorVersion);
glGetIntegerv(GL_MINOR_VERSION, &minorVersion);
if (glGetError() != GL_NO_ERROR) {
// This is not a GL {,ES} 3.0 context...
assert(false);
return false;
}
if (esProfile)
return true; // ES 3.0+ has swizzle
else if (majorVersion == 3 && minorVersion < 3)
return false; // Swizzle was introduced in OpenGL 3.3.
else
return true;
}
#if !defined(SDL_PLATFORM_IOS)
// SDL only defines this on IOS and on Windows undefined != 0
#define SDL_PLATFORM_IOS 0
#endif
GLint
GL3LoadTestSample::framebufferColorEncoding()
{
GLint encoding = GL_SRGB;
GLenum attachment;
#if !defined(GL_BACK_LEFT)
#define GL_BACK_LEFT 0x0402
#endif
if (strstr((const char*)glGetString(GL_VERSION), "GL ES") == NULL)
attachment = GL_BACK_LEFT;
else if (SDL_PLATFORM_IOS)
// iOS does not use the default framebuffer.
attachment = GL_COLOR_ATTACHMENT0;
else
attachment = GL_BACK;
glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, attachment,
GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING,
&encoding);
return encoding;
}
// Emscripten assimp port not yet available.
#if !defined(__EMSCRIPTEN__)
void
GL3LoadTestSample::loadMesh(std::string filename,
glMeshLoader::MeshBuffer& meshBuffer,
std::vector<glMeshLoader::VertexLayout> vertexLayout,
float scale)
{
GLMeshLoader *mesh = new GLMeshLoader();
mesh->LoadMesh(filename);
assert(mesh->m_Entries.size() > 0);
mesh->CreateBuffers(
meshBuffer,
vertexLayout,
scale);
meshBuffer.dim = mesh->dim.size;
delete(mesh);
}
#else
void
GL3LoadTestSample::loadMesh(std::string,
glMeshLoader::MeshBuffer&,
std::vector<glMeshLoader::VertexLayout>,
float)
{
}
#endif
@@ -0,0 +1,76 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2017-2020 Mark Callow, <khronos at callow dot im>.
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef GL3_LOAD_TEST_SAMPLE_H
#define GL3_LOAD_TEST_SAMPLE_H
#include <ktx.h>
#include "LoadTestSample.h"
#include "mygl.h"
#include "utils/GLMeshLoader.hpp"
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
class GL3LoadTestSample : public LoadTestSample {
public:
typedef uint64_t ticks_t;
GL3LoadTestSample(uint32_t width, uint32_t height,
const char* const /*szArgs*/,
const std::string sBasePath)
: LoadTestSample(width, height, sBasePath)
{
}
virtual ~GL3LoadTestSample() { }
virtual int doEvent(SDL_Event* event) { return LoadTestSample::doEvent(event); };
virtual void resize(uint32_t width, uint32_t height) = 0;
virtual void run(uint32_t msTicks) = 0;
//virtual void getOverlayText(TextOverlay *textOverlay) { };
typedef LoadTestSample* (*PFN_create)(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath);
protected:
using ShaderSource = std::vector<const GLchar*>;
virtual void keyPressed(uint32_t /*keyCode*/) { }
virtual void viewChanged() { }
static bool contextSupportsSwizzle();
std::string ktxfilename;
int externalFile = 0;
struct compressedTexFeatures {
bool astc_ldr;
bool astc_hdr;
bool bc6h;
bool bc7;
bool etc1;
bool etc2;
bool bc3;
bool pvrtc1;
bool pvrtc_srgb;
bool pvrtc2;
bool rgtc;
};
static void determineCompressedTexFeatures(compressedTexFeatures& features);
static GLint framebufferColorEncoding();
void loadMesh(std::string filename, glMeshLoader::MeshBuffer& meshBuffer,
std::vector<glMeshLoader::VertexLayout> vertexLayout,
float scale);
static void makeShader(GLenum type, ShaderSource& source, GLuint* shader);
static void makeShader(GLenum type, const GLchar* const source,
GLuint* shader);
static void makeProgram(GLuint vs, GLuint fs, GLuint* program);
};
#endif /* GL3_LOAD_TEST_SAMPLE_H */
@@ -0,0 +1,278 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2015-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief Instantiate GLLoadTests app with set of tests for OpenGL 3.3+ and
* OpenGL ES 3.x
*/
#include <string>
#include <regex>
#include "GLLoadTests.h"
#include "EncodeTexture.h"
#include "DrawTexture.h"
#include "TexturedCube.h"
#include "Texture3d.h"
#include "TextureCubemap.h"
#include "TextureArray.h"
#include "TextureMipmap.h"
#if !defined TEST_COMPRESSION
#define TEST_COMPRESSION 1
#endif
LoadTestSample*
GLLoadTests::showFile(const std::string& filename)
{
KTX_error_code ktxresult;
ktxTexture* kTexture;
ktxresult = ktxTexture_CreateFromNamedFile(filename.c_str(),
KTX_TEXTURE_CREATE_NO_FLAGS,
&kTexture);
if (KTX_SUCCESS != ktxresult) {
std::stringstream message;
message << "Creation of ktxTexture from \"" << filename
<< "\" failed: " << ktxErrorString(ktxresult);
throw std::runtime_error(message.str());
}
LoadTestSample::PFN_create createViewer;
LoadTestSample* pViewer;
if (kTexture->numDimensions == 3)
createViewer = Texture3d::create;
else if (kTexture->isArray && kTexture->isCubemap) {
// TODO: Add cubemap array app.
std::stringstream message;
message << "Display of cubemap array textures not yet implemented.";
throw std::runtime_error(message.str());
} else if (kTexture->isArray) {
createViewer = TextureArray::create;
} else if (kTexture->isCubemap) {
#if !defined(__EMSCRIPTEN__)
createViewer = TextureCubemap::create;
#else
throw std::runtime_error("Emscripten viewer can't display cube maps"
" because there is no libassimp support.");
#endif
} else if (kTexture->numLevels > 1 || kTexture->generateMipmaps) {
// TODO: Add option to choose to display showing the individual
// mipmaps vs. DrawTexture that displays a single rect using the
// mipmaps, if present.
createViewer = TextureMipmap::create;
} else {
createViewer = DrawTexture::create;
}
ktxTexture_Destroy(kTexture);
// Escape any spaces in filename.
std::string args = "--external " + std::regex_replace( filename, std::regex(" "), "\\ " );
pViewer = createViewer(w_width, w_height, args.c_str(), sBasePath);
return pViewer;
}
const GLLoadTests::sampleInvocation siSamples[] = {
{ DrawTexture::create,
"etc1s_Iron_Bars_001_normal.ktx2",
"Transcode of ETC1S+BasisLZ Compressed KTX2 XY normal map mipmapped"
},
{ DrawTexture::create,
"uastc_Iron_Bars_001_normal.ktx2",
"Transcode of UASTC+zstd Compressed KTX2 XY normal map mipmapped"
},
{ DrawTexture::create,
"color_grid_uastc_zstd.ktx2",
"Transcode of UASTC+Zstd Compressed KTX2 RGB not mipmapped "
},
{ DrawTexture::create,
"color_grid_zstd.ktx2",
"Zstd Compressed KTX2 RGB not mipmapped"
},
{ DrawTexture::create,
"color_grid_uastc.ktx2",
"Transcode of UASTC Compressed KTX2 RGB not mipmapped"
},
{ DrawTexture::create,
"color_grid_basis.ktx2",
"Transcode of ETC1S+BasisLZ Compressed KTX2 RGB not mipmapped"
},
{ DrawTexture::create,
"kodim17_basis.ktx2",
"Transcode of ETC1S+BasisLZ Compressed KTX2 RGB not mipmapped"
},
{ DrawTexture::create,
"--transcode-target RGBA4444 kodim17_basis.ktx2",
"Transcode of ETC1S+BasisLZ Compressed KTX2 RGB not mipmapped to RGBA4444"
},
{ EncodeTexture::create,
"FlightHelmet_baseColor_basis.ktx2",
"Transcode of ETC1S+BasisLZ Compressed KTX2 RGBA not mipmapped"
},
#if TEST_COMPRESSION
{ EncodeTexture::create,
"--encode etc1s rgba-reference-u.ktx2",
"Encode to ETC1S+BasisLZ then Transcode of Compressed KTX2 RGBA not mipmapped"
},
{ EncodeTexture::create,
"--encode uastc rgba-reference-u.ktx2",
"Encode to UASTC then Transcode of Compressed KTX2 RGBA not mipmapped"
},
{ EncodeTexture::create,
"--encode astc rgba-reference-u.ktx2",
"Encode to ASTC then display RGBA not mipmapped"
},
#endif
#if !defined(__EMSCRIPTEN__)
{ TextureCubemap::create,
"cubemap_goldengate_uastc_rdo4_zstd5_rd.ktx2",
"Transcode of UASTC+rdo+zstd Compressed KTX2 Cube Map Transcoded"
},
{ TextureCubemap::create,
"cubemap_yokohama_basis_rd.ktx2",
"Transcode of ETC1S/BasisLZ Compressed KTX2 mipmapped cube map",
},
#endif
{ DrawTexture::create,
"orient-down-metadata-u.ktx2",
"KTX2: RGB8 + KTXOrientation down"
},
{ DrawTexture::create,
"--preload orient-down-metadata-u.ktx2",
"KTX2: RGB8 + KTXOrientation down with pre-loaded images"
},
{ TextureArray::create,
"texturearray_bc3_unorm.ktx2",
"KTX2: BC3 (S3TC DXT5) Compressed Texture Array"
},
{ TextureArray::create,
"texturearray_astc_8x8_unorm.ktx2",
"KTX2: ASTC 8x8 Compressed Texture Array"
},
{ TextureArray::create,
"texturearray_etc2_unorm.ktx2",
"KTX2: ETC2 Compressed Texture Array"
},
{ Texture3d::create,
"3dtex_7_reference_u.ktx2",
"RGBA8 3d Texture, Depth == 7"
},
{ TexturedCube::create,
"rgb-mipmap-reference-u.ktx2",
"KTX2: RGB8 Color/level mipmap"
},
{ DrawTexture::create,
"hi_mark.ktx",
"RGB8 NPOT HI Logo"
},
{ DrawTexture::create,
"orient-up-metadata.ktx",
"RGB8 + KTXOrientation up"
},
{ DrawTexture::create,
"orient-down-metadata.ktx",
"RGB8 + KTXOrientation down"
},
{ DrawTexture::create,
"not4_rgb888_srgb.ktx",
"RGB8 2D, Row length not Multiple of 4"
},
{ DrawTexture::create,
"etc1.ktx",
"ETC1 RGB8"
},
{ DrawTexture::create,
"etc2-rgb.ktx",
"ETC2 RGB8"
},
{ DrawTexture::create,
"etc2-rgba1.ktx",
"ETC2 RGB8A1"
},
{ DrawTexture::create,
"etc2-rgba8.ktx",
"ETC2 RGB8A8"
},
{ DrawTexture::create,
"etc2-sRGB.ktx",
"ETC2 sRGB8"
},
{ DrawTexture::create,
"etc2-sRGBa1.ktx",
"ETC2 sRGB8A1"
},
{ DrawTexture::create,
"etc2-sRGBa8.ktx",
"ETC2 sRGB8A8"
},
{ DrawTexture::create,
"rgba-reference.ktx",
"RGBA8"
},
{ DrawTexture::create,
"rgb-reference.ktx",
"RGB8"
},
{ DrawTexture::create,
"conftestimage_R11_EAC.ktx",
"ETC2 R11"
},
{ DrawTexture::create,
"conftestimage_SIGNED_R11_EAC.ktx",
"ETC2 Signed R11"
},
{ DrawTexture::create,
"conftestimage_RG11_EAC.ktx",
"ETC2 RG11"
},
{ DrawTexture::create,
"conftestimage_SIGNED_RG11_EAC.ktx",
"ETC2 Signed RG11"
},
{ TextureArray::create,
"texturearray_bc3_unorm.ktx",
"BC3 (S3TC DXT5) Compressed Texture Array"
},
{ TextureArray::create,
"texturearray_astc_8x8_unorm.ktx",
"ASTC 8x8 Compressed Texture Array"
},
{ TextureArray::create,
"texturearray_etc2_unorm.ktx",
"ETC2 Compressed Texture Array"
},
{ TexturedCube::create,
"rgb-amg-reference.ktx",
"RGB8 + Auto Mipmap"
},
{ TexturedCube::create,
"rgb-mipmap-reference.ktx",
"RGB8 Color/level mipmap"
},
{ TexturedCube::create,
"hi_mark_sq.ktx",
"RGB8 NPOT HI Logo"
},
};
const uint32_t uNumSamples = sizeof(siSamples) / sizeof(GLLoadTests::sampleInvocation);
#if !(defined(GL_CONTEXT_PROFILE) && defined(GL_CONTEXT_MAJOR_VERSION) && defined(GL_CONTEXT_MINOR_VERSION))
#error GL_CONTEXT_PROFILE, GL_CONTEXT_MAJOR_VERSION & GL_CONTEXT_MINOR_VERSION must be defined.
#endif
AppBaseSDL* theApp = new GLLoadTests(siSamples, uNumSamples,
"KTX Loader Tests for GL3 & ES3",
GL_CONTEXT_PROFILE,
GL_CONTEXT_MAJOR_VERSION,
GL_CONTEXT_MINOR_VERSION);
@@ -0,0 +1,476 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2018-2021 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief Base for samplesusing instancing such as array texture display.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <algorithm>
#include <time.h>
#include <sstream>
#include <vector>
#include <ktx.h>
#include "argparser.h"
#include "InstancedSampleBase.h"
#include "TranscodeTargetStrToFmt.h"
#include "GLTextureTranscoder.hpp"
#include "ltexceptions.h"
#define member_size(type, member) sizeof(((type *)0)->member)
using namespace std;
const GLchar* InstancedSampleBase::pszInstancingFsDeclarations =
"precision mediump float;\n"
"in vec3 UVW;\n\n"
"layout (location = 0) out vec4 outFragColor;\n\n";
const GLchar* InstancedSampleBase::pszSrgbEncodeFunc =
"vec3 srgb_encode(vec3 color) {\n"
" float r = color.r < 0.0031308 ? 12.92 * color.r : 1.055 * pow(color.r, 1.0/2.4) - 0.055;\n"
" float g = color.g < 0.0031308 ? 12.92 * color.g : 1.055 * pow(color.g, 1.0/2.4) - 0.055;\n"
" float b = color.b < 0.0031308 ? 12.92 * color.b : 1.055 * pow(color.b, 1.0/2.4) - 0.055;\n"
" return vec3(r, g, b);\n"
"}\n\n";
const GLchar* InstancedSampleBase::pszInstancingFsMain =
"void main()\n"
"{\n"
" outFragColor = texture(uSampler, UVW);\n"
"}";
const GLchar* InstancedSampleBase::pszInstancingSrgbEncodeFsMain =
"void main()\n"
"{\n"
" vec4 t_color = texture(uSampler, UVW);\n"
" outFragColor.rgb = srgb_encode(t_color.rgb);\n"
" outFragColor.a = t_color.a;\n"
"}";
const GLchar* InstancedSampleBase::pszInstancingVsDeclarations =
"layout (location = 0) in vec4 inPos;\n"
"layout (location = 1) in vec2 inUV;\n\n"
"struct Instance\n"
"{\n"
" mat4 model;\n"
"};\n\n"
"//layout (binding = 0) uniform UBO\n"
"layout(std140) uniform UBO\n"
"{\n"
" mat4 projection;\n"
" mat4 view;\n"
" Instance instance[INSTANCE_COUNT];\n"
"} ubo;\n\n"
"out vec3 UVW;\n\n";
/* ------------------------------------------------------------------------- */
/**
* @internal
* @class InstancedSampleBase
* @~English
*
* @brief Test loading of 2D texture arrays.
*/
InstancedSampleBase::InstancedSampleBase(uint32_t width, uint32_t height,
const char* const szArgs,
const string sBasePath)
: GL3LoadTestSample(width, height, szArgs, sBasePath),
texUnit(GL_TEXTURE0), uniformBufferBindId(0),
bInitialized(false)
{
zoom = -15.0f;
rotationSpeed = 0.25f;
//rotation = glm::vec3(-15.0f, 35.0f, 0.0f);
rotation = glm::vec3(15.0f, 35.0f, 0.0f);
gnTexture = 0;
// Ensure we're using the desired unit
glActiveTexture(texUnit);
processArgs(szArgs);
KTX_error_code ktxresult;
ktxTexture* kTexture;
GLenum glerror;
string ktxfilepath = externalFile ? ktxfilename
: getAssetPath() + ktxfilename;
ktxresult =
ktxTexture_CreateFromNamedFile(ktxfilepath.c_str(),
KTX_TEXTURE_CREATE_NO_FLAGS,
&kTexture);
if (KTX_SUCCESS != ktxresult) {
std::stringstream message;
message << "Creation of ktxTexture from \"" << ktxfilepath
<< "\" failed: " << ktxErrorString(ktxresult);
throw std::runtime_error(message.str());
}
if (ktxTexture_NeedsTranscoding(kTexture)) {
transcodeTarget = KTX_TTF_NOSELECTION;
TextureTranscoder tc;
tc.transcode((ktxTexture2*)kTexture, transcodeTarget);
}
ktxresult = ktxTexture_GLUpload(kTexture, &gnTexture, &texTarget,
&glerror);
if (KTX_SUCCESS != ktxresult) {
std::stringstream message;
message << "ktxTexture_GLUpload failed: ";
if (ktxresult != KTX_GL_ERROR) {
message << ktxErrorString(ktxresult);
throw std::runtime_error(message.str());
} else if (kTexture->isCompressed && glerror == GL_INVALID_ENUM) {
throw unsupported_ctype();
} else {
message << std::showbase << "GL error " << std::hex << glerror
<< " occurred.";
throw std::runtime_error(message.str());
}
}
if (kTexture->generateMipmaps) {
// GLUpload will have generated the mipmaps already.
uint32_t maxDim = (std::max)(
(std::max)(kTexture->baseWidth, kTexture->baseHeight),
kTexture->baseDepth);
textureInfo.numLevels = (uint32_t)floor(log2(maxDim)) + 1;
} else {
textureInfo.numLevels = kTexture->numLevels;
}
textureInfo.numLayers = kTexture->numLayers;
textureInfo.baseDepth = kTexture->baseDepth;
if (textureInfo.numLevels > 1)
bIsMipmapped = true;
else
bIsMipmapped = false;
// Checking if KVData contains keys of interest would go here.
ktxTexture_Destroy(kTexture);
}
InstancedSampleBase::~InstancedSampleBase()
{
cleanup();
}
void
InstancedSampleBase::resize(uint32_t width, uint32_t height)
{
this->w_width = width;
this->w_height = height;
glViewport(0, 0, width, height);
updateUniformBufferMatrices();
}
void
InstancedSampleBase::run(uint32_t /*msTicks*/)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Keep these permanently bound
//glBindVertexArray(gnVao);
// Must be done after the VAO is bound
//glBindBuffer(GL_ARRAY_BUFFER, gnVbo[0]);
//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gnVbo[1]);
glDrawElementsInstanced(GL_TRIANGLES, quad.indexCount,
GL_UNSIGNED_INT, (GLvoid*)quad.indicesOffset,
instanceCount);
assert(GL_NO_ERROR == glGetError());
}
//===================================================================
void
InstancedSampleBase::processArgs(string sArgs)
{
// Options descriptor
struct argparser::option longopts[] = {
{"external", argparser::option::no_argument, &externalFile, 1},
{"transcode-target", argparser::option::required_argument, nullptr, 2},
{NULL, argparser::option::no_argument, NULL, 0}
};
argvector argv(sArgs);
argparser ap(argv);
int ch;
while ((ch = ap.getopt(nullptr, longopts, nullptr)) != -1) {
switch (ch) {
case 0: break;
case 2:
transcodeTarget = TranscodeTargetStrToFmt(ap.optarg);
break;
default: assert(false); // Error in args in sample table.
}
}
assert(ap.optind < argv.size());
ktxfilename = argv[ap.optind];
}
/* ------------------------------------------------------------------------- */
void
InstancedSampleBase::cleanup()
{
glEnable(GL_DITHER);
glEnable(GL_CULL_FACE);
glFrontFace(GL_CCW);
glDisable(GL_DEPTH_TEST);
if (bInitialized) {
glUseProgram(0);
glDeleteTextures(1, &gnTexture);
glDeleteProgram(gnInstancingProg);
glDeleteBuffers(2, quad.gnVbo);
glDeleteVertexArrays(1, &quad.gnVao);
delete uboVS.instance;
}
assert(GL_NO_ERROR == glGetError());
}
// Setup vertices for a single uv-mapped quad
void
InstancedSampleBase::generateQuad()
{
#define dim 2.5f
//std::vector<TAVertex> vertices =
TAVertex vertices[] =
{
{ { dim, dim, 0.0f }, { 1.0f, 1.0f } },
{ { -dim, dim, 0.0f }, { 0.0f, 1.0f } },
{ { -dim, -dim, 0.0f }, { 0.0f, 0.0f } },
{ { dim, -dim, 0.0f }, { 1.0f, 0.0f } }
};
#undef dim
// Setup indices
uint32_t indices[] = { 0,1,2, 2,3,0 };
quad.indexCount = static_cast<uint32_t>(ARRAY_LEN(indices));
// Create a VAO and bind it.
glGenVertexArrays(1, &quad.gnVao);
glBindVertexArray(quad.gnVao);
// Must have vertex data in buffer objects to use VAO's on ES3/GL Core
glGenBuffers(2, quad.gnVbo);
glBindBuffer(GL_ARRAY_BUFFER, quad.gnVbo[0]);
// Must be done after the VAO is bound
// WebGL requires different buffers for data and indices.
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quad.gnVbo[1]);
// Create the buffer data store.
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), NULL, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
GLsizeiptr offset = 0;
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(vertices), vertices);
quad.verticesOffset = offset;
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(TAVertex),
(GLvoid*)offset);
offset += member_size(TAVertex, pos);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE,
sizeof(TAVertex), (GLvoid*)offset);
offset = sizeof(vertices);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices),
indices, GL_STATIC_DRAW);
quad.indicesOffset = 0;
}
#define _PAD16(nbytes) (ktx_uint32_t)(16 * ceilf((float)(nbytes) / 16))
void
InstancedSampleBase::prepareUniformBuffers()
{
uProgramUniforms = glGetUniformBlockIndex(gnInstancingProg, "UBO");
if (uProgramUniforms == -1) {
std::stringstream message;
message << "prepareUniformBuffers: UBO not found in program";
throw std::runtime_error(message.str());
}
// INSTANCE_COUNT is set in GLSL code via define set in prepareProgram.
//
// Elements of the array of UboInstanceData will be aligned on 16-byte
// boundaries per the std140 rule for mat4/vec4. _PAD16 is unnecessary
// right now but will become so if anything is added to the ubo before
// the UboInstanceData. _PAD16 is put here as a warning.
uint32_t uboSize = _PAD16(sizeof(uboVS.matrices))
+ instanceCount * sizeof(UboInstanceData);
uboVS.instance = new UboInstanceData[instanceCount];
glGenBuffers(1, &gnUbo);
glBindBuffer(GL_UNIFORM_BUFFER, gnUbo);
// Create the data store.
glBufferData(GL_UNIFORM_BUFFER, uboSize, 0, GL_DYNAMIC_DRAW);
float offset = 1.5f;
float center = (instanceCount * offset) / 2;
for (uint32_t i = 0; i < instanceCount; i++)
{
// Instance model matrix
uboVS.instance[i].model
= glm::translate(glm::mat4(), glm::vec3(0.0f,
i * offset - center, 0.0f));
uboVS.instance[i].model = glm::rotate(uboVS.instance[i].model,
glm::radians(120.0f),
glm::vec3(1.0f, 0.0f, 0.0f));
}
// Update instanced part of the uniform buffer
// N.B. See comment re _PAD16 before uboSize above.
uint32_t dataOffset = _PAD16(sizeof(uboVS.matrices));
uint32_t dataSize = instanceCount * sizeof(UboInstanceData);
glBufferSubData(GL_UNIFORM_BUFFER, dataOffset, dataSize, uboVS.instance);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
glBindBufferBase(GL_UNIFORM_BUFFER, uniformBufferBindId, gnUbo);
glUseProgram(gnInstancingProg);
glUniformBlockBinding(gnInstancingProg, uProgramUniforms,
uniformBufferBindId);
updateUniformBufferMatrices();
glUseProgram(0);
assert(glGetError() == GL_NO_ERROR);
}
void
InstancedSampleBase::updateUniformBufferMatrices()
{
// Only updates the uniform buffer block part containing the global matrices
// Projection
uboVS.matrices.projection = glm::perspective(glm::radians(60.0f),
(float)w_width / w_height,
.01f, 256.f);
// View
uboVS.matrices.view = glm::translate(glm::mat4(),
glm::vec3(0.0f, 1.0f, zoom));
uboVS.matrices.view *= glm::translate(glm::mat4(), cameraPos);
uboVS.matrices.view = glm::rotate(uboVS.matrices.view,
glm::radians(rotation.x),
glm::vec3(1.0f, 0.0f, 0.0f));
uboVS.matrices.view = glm::rotate(uboVS.matrices.view,
glm::radians(rotation.y),
glm::vec3(0.0f, 1.0f, 0.0f));
uboVS.matrices.view = glm::rotate(uboVS.matrices.view,
glm::radians(rotation.z),
glm::vec3(0.0f, 0.0f, 1.0f));
#if !defined(EMSCRIPTEN)
// Only update the matrices part of the uniform buffer
uint8_t *pData = (uint8_t*)glMapBufferRange(GL_UNIFORM_BUFFER, 0,
sizeof(uboVS.matrices),
GL_MAP_WRITE_BIT);
memcpy(pData, &uboVS.matrices, sizeof(uboVS.matrices));
glUnmapBuffer(GL_UNIFORM_BUFFER);
#else
glBufferSubData(GL_UNIFORM_BUFFER, 0,
sizeof(uboVS.matrices), &uboVS.matrices);
#endif
}
void
InstancedSampleBase::prepareSampler()
{
glBindTexture(texTarget, gnTexture);
if (bIsMipmapped)
glTexParameteri(texTarget,
GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
else
glTexParameteri(texTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(texTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(texTarget, 0);
glUseProgram(gnInstancingProg);
if ((uSampler = glGetUniformLocation(gnInstancingProg,
"uSampler")) == -1) {
std::stringstream message;
message << "prepareSampler: uSampler not found in program";
throw std::runtime_error(message.str());
}
glUniform1i(uSampler, texUnit - GL_TEXTURE0);
glUseProgram(0);
}
void
InstancedSampleBase::prepareProgram(ShaderSource& fs, ShaderSource& vs)
{
GLuint gnInstancingFs, gnInstancingVs;
try {
std::stringstream ssDefine;
ssDefine << "#define INSTANCE_COUNT " << instanceCount << "U" << endl;
// str().c_str() doesn't work because str goes outof scope immediately.
// Hence this 2 step process.
string sDefine = ssDefine.str();
vs.insert(vs.begin(), sDefine.c_str());
makeShader(GL_VERTEX_SHADER, vs, &gnInstancingVs);
makeShader(GL_FRAGMENT_SHADER, fs, &gnInstancingFs);
makeProgram(gnInstancingVs, gnInstancingFs, &gnInstancingProg);
} catch (std::exception& e) {
(void)e; // To quiet unused variable warnings from some compilers.
throw;
}
glDeleteShader(gnInstancingVs);
glDeleteShader(gnInstancingFs);
}
void
InstancedSampleBase::prepare(ShaderSource& fs, ShaderSource& vs)
{
// By default dithering is enabled. Dithering does not provide visual
// improvement in this sample so disable it to improve performance.
glDisable(GL_DITHER);
glFrontFace(GL_CW);
glDisable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
glClearColor(0.2f,0.3f,0.4f,1.0f);
//prepareSamplerAndView();
//setupVertexDescriptions();
generateQuad();
prepareProgram(fs, vs);
prepareUniformBuffers();
prepareSampler();
glUseProgram(gnInstancingProg);
glBindTexture(texTarget, gnTexture);
}
@@ -0,0 +1,121 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2018-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _INSTANCE_SAMPLE_BASE_H_
#define _INSTANCE_SAMPLE_BASE_H_
#include <vector>
#include "GL3LoadTestSample.h"
#include <glm/gtc/matrix_transform.hpp>
#define VERTEX_BUFFER_BIND_ID 0
#define ENABLE_VALIDATION false
class InstancedSampleBase : public GL3LoadTestSample
{
public:
InstancedSampleBase(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath);
~InstancedSampleBase();
virtual void resize(uint32_t width, uint32_t height);
virtual void run(uint32_t msTicks);
//virtual void getOverlayText(VulkanTextOverlay *textOverlay);
protected:
using ShaderSource = GL3LoadTestSample::ShaderSource;
const GLuint texUnit;
const GLuint uniformBufferBindId;
GLenum texTarget;
GLuint gnTexture;
GLuint gnInstancingProg;
GLuint gnUbo;
ktx_transcode_fmt_e transcodeTarget;
bool bInitialized;
bool bIsMipmapped;
struct textureInfo {
uint32_t numLayers;
uint32_t numLevels;
uint32_t baseDepth;
} textureInfo;
uint32_t instanceCount;
// Vertex layout for this example
struct TAVertex {
float pos[3];
float uv[2];
};
struct MeshBuffer {
uint32_t indexCount;
glm::vec3 dim;
GLuint gnVao;
GLuint gnVbo[2];
GLsizeiptr verticesOffset;
GLsizeiptr indicesOffset;
};
MeshBuffer quad;
struct UboInstanceData {
// Model matrix
glm::mat4 model;
};
struct {
// Global matrices
struct {
glm::mat4 projection;
glm::mat4 view;
} matrices;
// N.B. The UBO structure declared in the shader has the array of
// instance data inside the structure rather than pointed at from the
// structure. The start of the array will be aligned on a 16-byte
// boundary as it starts with a matrix.
//
// Separate data for each instance
UboInstanceData *instance;
} uboVS;
GLint uProgramUniforms;
GLint uSampler;
static const GLchar* pszInstancingFsDeclarations;
static const GLchar* pszSrgbEncodeFunc;
static const GLchar* pszInstancingFsMain;
static const GLchar* pszInstancingSrgbEncodeFsMain;
static const GLchar* pszInstancingVsDeclarations;
void cleanup();
// Setup vertices for a single uv-mapped quad
void generateQuad();
void prepareUniformBuffers();
void updateUniformBufferMatrices();
void prepareSampler();
void prepareProgram(ShaderSource& fs, ShaderSource& vs);
void prepare(ShaderSource& fs, ShaderSource& vs);
void processArgs(std::string sArgs);
virtual void viewChanged()
{
updateUniformBufferMatrices();
}
};
#endif /* _INSTANCE_SAMPLE_BASE_H_ */
@@ -0,0 +1,107 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2021 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief Definition of test sample for loading and displaying the slices of a 3d texture.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <algorithm>
#include <time.h>
#include <sstream>
#include <vector>
#include <ktx.h>
#include "argparser.h"
#include "Texture3d.h"
#include "ltexceptions.h"
#define member_size(type, member) sizeof(((type *)0)->member)
const GLchar* pszFsSampler3dDeclaration =
"uniform mediump sampler3D uSampler;\n\n";
const GLchar* psz3dVsMain =
"void main()\n"
"{\n"
" UVW = vec3(inUV, float(gl_InstanceID) / float(INSTANCE_COUNT - 1U));\n"
" mat4 modelView = ubo.view * ubo.instance[gl_InstanceID].model;\n"
" gl_Position = ubo.projection * modelView * inPos;\n"
"}";
/* ------------------------------------------------------------------------- */
LoadTestSample*
Texture3d::create(uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath)
{
return new Texture3d(width, height, szArgs, sBasePath);
}
/**
* @internal
* @class Texture3d
* @~English
*
* @brief Test loading of 2D texture arrays.
*/
Texture3d::Texture3d(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath)
: InstancedSampleBase(width, height, szArgs, sBasePath)
{
zoom = -15.0f;
if (texTarget != GL_TEXTURE_3D) {
std::stringstream message;
message << "Texture3d requires an 3d texture.";
throw std::runtime_error(message.str());
}
// Checking if KVData contains keys of interest would go here.
instanceCount = textureInfo.baseDepth;
InstancedSampleBase::ShaderSource fs;
InstancedSampleBase::ShaderSource vs;
fs.push_back(pszInstancingFsDeclarations);
fs.push_back(pszFsSampler3dDeclaration);
if (framebufferColorEncoding() == GL_LINEAR) {
fs.push_back(pszSrgbEncodeFunc);
fs.push_back(pszInstancingSrgbEncodeFsMain);
} else {
fs.push_back(pszInstancingFsMain);;
}
vs.push_back(pszInstancingVsDeclarations);
vs.push_back(psz3dVsMain);
try {
prepare(fs, vs);
} catch (std::exception& e) {
(void)e; // To quiet unused variable warnings from some compilers.
cleanup();
throw;
}
// Texture was bound by prepare()
// Set this so it is easier to recognize that the texture has the expected
// slices.
glTexParameteri(texTarget, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
bInitialized = true;
}
@@ -0,0 +1,41 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2021 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief Declaration of test sample for loading and displaying the slices of a 3d texture..
*
* @author Mark Callow, github.com/MarkCallow.
*/
#include <vector>
#include "InstancedSampleBase.h"
#include <glm/gtc/matrix_transform.hpp>
#define VERTEX_BUFFER_BIND_ID 0
#define ENABLE_VALIDATION false
class Texture3d : public InstancedSampleBase
{
public:
Texture3d(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath);
static LoadTestSample*
create(uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath);
protected:
using ShaderSource = GL3LoadTestSample::ShaderSource;
};
@@ -0,0 +1,100 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2018-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief Definition of test sample for loading and displaying the layers of a 2D array texture.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <algorithm>
#include <time.h>
#include <sstream>
#include <vector>
#include <ktx.h>
#include "argparser.h"
#include "TextureArray.h"
#include "ltexceptions.h"
#define member_size(type, member) sizeof(((type *)0)->member)
const GLchar* pszFsArraySamplerDeclaration =
"uniform mediump sampler2DArray uSampler;\n\n";
const GLchar* pszArrayVsMain =
"void main()\n"
"{\n"
" UVW = vec3(inUV, gl_InstanceID);\n"
" mat4 modelView = ubo.view * ubo.instance[gl_InstanceID].model;\n"
" gl_Position = ubo.projection * modelView * inPos;\n"
"}";
/* ------------------------------------------------------------------------- */
LoadTestSample*
TextureArray::create(uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath)
{
return new TextureArray(width, height, szArgs, sBasePath);
}
/**
* @internal
* @class TextureArray
* @~English
*
* @brief Test loading of 2D texture arrays.
*/
TextureArray::TextureArray(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath)
: InstancedSampleBase(width, height, szArgs, sBasePath)
{
zoom = -15.0f;
if (texTarget != GL_TEXTURE_2D_ARRAY) {
std::stringstream message;
message << "TextureArray requires an array texture.";
throw std::runtime_error(message.str());
}
// Checking if KVData contains keys of interest would go here.
instanceCount = textureInfo.numLayers;
InstancedSampleBase::ShaderSource fs;
InstancedSampleBase::ShaderSource vs;
fs.push_back(pszInstancingFsDeclarations);
fs.push_back(pszFsArraySamplerDeclaration);
if (framebufferColorEncoding() == GL_LINEAR) {
fs.push_back(pszSrgbEncodeFunc);
fs.push_back(pszInstancingSrgbEncodeFsMain);
} else {
fs.push_back(pszInstancingFsMain);;
}
vs.push_back(pszInstancingVsDeclarations);
vs.push_back(pszArrayVsMain);
try {
prepare(fs, vs);
} catch (std::exception& e) {
(void)e; // To quiet unused variable warnings from some compilers.
cleanup();
throw;
}
bInitialized = true;
}
@@ -0,0 +1,41 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2021 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief Declaration of test sample for loading and displaying the layers of a 2D array texture.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#include <vector>
#include "InstancedSampleBase.h"
#include <glm/gtc/matrix_transform.hpp>
#define VERTEX_BUFFER_BIND_ID 0
#define ENABLE_VALIDATION false
class TextureArray : public InstancedSampleBase
{
public:
TextureArray(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath);
static LoadTestSample*
create(uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath);
protected:
using ShaderSource = GL3LoadTestSample::ShaderSource;
};
@@ -0,0 +1,710 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief Test loading of 2D texture arrays.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <algorithm>
#include <time.h>
#include <sstream>
#include <vector>
#include <ktx.h>
#include "argparser.h"
#include "TextureCubemap.h"
#include "GLTextureTranscoder.hpp"
#include "SwipeDetector.h"
#include "ltexceptions.h"
#define member_size(type, member) sizeof(((type *)0)->member)
const GLchar* pszReflectFs =
"precision highp float;"
"uniform UBO\n"
"{\n"
" mat4 projection;\n"
" mat4 modelView;\n"
" mat4 skyboxView;\n"
" mat4 invModelView;\n"
" mat4 uvwTransform;\n"
" float lodBias;\n"
"} ubo;\n\n"
"uniform samplerCube uSamplerColor;\n\n"
"in vec3 vPos;\n"
"in vec3 vNormal;\n"
"in float vLodBias;\n"
"in vec3 vViewVec;\n"
"in vec3 vLightVec;\n\n"
"layout (location = 0) out vec4 outFragColor;\n\n"
"void main()\n"
"{\n"
" vec3 cI = normalize (vPos);\n"
" vec3 cR = reflect (cI, normalize(vNormal));\n\n"
" cR = vec3(ubo.uvwTransform * ubo.invModelView * vec4(cR, 0.0));\n\n"
" vec4 color = texture(uSamplerColor, cR, vLodBias);\n\n"
" vec3 N = normalize(vNormal);\n"
" vec3 L = normalize(vLightVec);\n"
" vec3 V = normalize(vViewVec);\n"
" vec3 R = reflect(-L, N);\n"
" vec3 ambient = vec3(0.5) * color.rgb;\n"
" vec3 diffuse = max(dot(N, L), 0.0) * vec3(1.0);\n"
" vec3 specular = pow(max(dot(R, V), 0.0), 16.0) * vec3(0.5);\n"
" outFragColor = vec4(ambient + diffuse * color.rgb + specular, 1.0);\n"
"}\n";
const GLchar* pszReflectSrgbEncodeFs =
"precision highp float;"
"uniform UBO\n"
"{\n"
" mat4 projection;\n"
" mat4 modelView;\n"
" mat4 skyboxView;\n"
" mat4 invModelView;\n"
" mat4 uvwTransform;\n"
" float lodBias;\n"
"} ubo;\n\n"
"uniform samplerCube uSamplerColor;\n\n"
"in vec3 vPos;\n"
"in vec3 vNormal;\n"
"in float vLodBias;\n"
"in vec3 vViewVec;\n"
"in vec3 vLightVec;\n\n"
"layout (location = 0) out vec4 outFragColor;\n\n"
"vec3 srgb_encode(vec3 color) {\n"
" float r = color.r < 0.0031308 ? 12.92 * color.r : 1.055 * pow(color.r, 1.0/2.4) - 0.055;\n"
" float g = color.g < 0.0031308 ? 12.92 * color.g : 1.055 * pow(color.g, 1.0/2.4) - 0.055;\n"
" float b = color.b < 0.0031308 ? 12.92 * color.b : 1.055 * pow(color.b, 1.0/2.4) - 0.055;\n"
" return vec3(r, g, b);\n"
"}\n\n"
"void main()\n"
"{\n"
" vec3 cI = normalize (vPos);\n"
" vec3 cR = reflect (cI, normalize(vNormal));\n\n"
" cR = vec3(ubo.uvwTransform * ubo.invModelView * vec4(cR, 0.0));\n\n"
" vec4 color = texture(uSamplerColor, cR, vLodBias);\n\n"
" vec3 N = normalize(vNormal);\n"
" vec3 L = normalize(vLightVec);\n"
" vec3 V = normalize(vViewVec);\n"
" vec3 R = reflect(-L, N);\n"
" vec3 ambient = vec3(0.5) * color.rgb;\n"
" vec3 diffuse = max(dot(N, L), 0.0) * vec3(1.0);\n"
" vec3 specular = pow(max(dot(R, V), 0.0), 16.0) * vec3(0.5);\n"
" color.rgb = srgb_encode(ambient + diffuse * color.rgb + specular);\n"
" outFragColor = vec4(color.rgb, 1.0);\n"
"}\n";
const GLchar* pszReflectVs =
"precision highp float;"
"layout (location = 0) in vec3 inPos;\n"
"layout (location = 1) in vec3 inNormal;\n\n"
"uniform UBO\n"
"{\n"
" mat4 projection;\n"
" mat4 modelView;\n"
" mat4 skyboxView;\n"
" mat4 invModelView;\n"
" mat4 uvwTransform;\n"
" float lodBias;\n"
"} ubo;\n"
"\n"
"out vec3 vPos;\n"
"out vec3 vNormal;\n"
"out float vLodBias;\n"
"out vec3 vViewVec;\n"
"out vec3 vLightVec;\n\n"
"void main()\n"
"{\n"
" gl_Position = ubo.projection * ubo.modelView * vec4(inPos, 1.0);\n\n"
" vPos = vec3(ubo.modelView * vec4(inPos, 1.0));\n"
" vNormal = mat3(ubo.modelView) * inNormal;\n"
" vLodBias = ubo.lodBias;\n\n"
" vec3 lightPos = vec3(0.0f, -5.0f, 5.0f);\n"
" vLightVec = lightPos.xyz - vPos.xyz;\n"
" vViewVec = -vPos.xyz;\n"
"}\n";
const GLchar* pszSkyboxFs =
"precision highp float;"
"uniform samplerCube uSamplerColor;\n\n"
"in vec3 vUVW;\n\n"
"layout (location = 0) out vec4 outFragColor;\n\n"
"void main()\n"
"{\n"
" outFragColor = texture(uSamplerColor, vUVW);\n"
"}\n";
const GLchar* pszSkyboxSrgbEncodeFs =
"precision highp float;"
"uniform samplerCube uSamplerColor;\n\n"
"in vec3 vUVW;\n\n"
"layout (location = 0) out vec4 outFragColor;\n\n"
"vec3 srgb_encode(vec3 color) {\n"
" float r = color.r < 0.0031308 ? 12.92 * color.r : 1.055 * pow(color.r, 1.0/2.4) - 0.055;\n"
" float g = color.g < 0.0031308 ? 12.92 * color.g : 1.055 * pow(color.g, 1.0/2.4) - 0.055;\n"
" float b = color.b < 0.0031308 ? 12.92 * color.b : 1.055 * pow(color.b, 1.0/2.4) - 0.055;\n"
" return vec3(r, g, b);\n"
"}\n\n"
"void main()\n"
"{\n"
" vec4 color = texture(uSamplerColor, vUVW);\n"
" outFragColor.rgb = srgb_encode(color.rgb);\n"
" outFragColor.a = color.a;\n"
"}\n";
const GLchar* pszSkyboxVs =
"precision highp float;"
"layout (location = 0) in vec3 inPos;\n\n"
"uniform UBO\n"
"{\n"
" mat4 projection;\n"
" mat4 modelView;\n"
" mat4 skyboxView;\n"
" mat4 invModelView;\n"
" mat4 uvwTransform;\n"
"} ubo;\n\n"
"out vec3 vUVW;\n\n"
"void main()\n"
"{\n"
" vUVW = (ubo.uvwTransform * vec4(inPos.xyz, 1.0)).xyz;\n"
" //vUVW = inPos.xyz;\n"
" gl_Position = (ubo.projection * ubo.skyboxView * vec4(inPos.xyz, 1.0)).xyww;\n"
"}\n";
/* ------------------------------------------------------------------------- */
// Vertex layout for this example
std::vector<glMeshLoader::VertexLayout> vertexLayout =
{
glMeshLoader::VERTEX_LAYOUT_POSITION,
glMeshLoader::VERTEX_LAYOUT_NORMAL,
glMeshLoader::VERTEX_LAYOUT_UV
};
LoadTestSample*
TextureCubemap::create(uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath)
{
return new TextureCubemap(width, height, szArgs, sBasePath);
}
/**
* @internal
* @class TextureCubemap
* @~English
*
* @brief Test loading of 2D texture arrays.
*/
TextureCubemap::TextureCubemap(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath)
: GL3LoadTestSample(width, height, szArgs, sBasePath),
cubemapTexUnit(GL_TEXTURE0), uniformBufferBindId(0),
bInitialized(false)
{
zoom = -4.0f;
rotationSpeed = 0.25f;
rotation = { -7.25f, 120.0f, 0.0f };
gnCubemapTexture = 0;
// Ensure we're using the desired unit
glActiveTexture(cubemapTexUnit);
processArgs(szArgs);
KTX_error_code ktxresult;
ktxTexture* kTexture;
GLenum glerror;
std::string ktxfilepath = externalFile ? ktxfilename
: getAssetPath() + ktxfilename;
ktxresult = ktxTexture_CreateFromNamedFile(ktxfilepath.c_str(),
KTX_TEXTURE_CREATE_NO_FLAGS,
&kTexture);
if (KTX_SUCCESS != ktxresult) {
std::stringstream message;
message << "Creation of ktxTexture from \"" << ktxfilepath
<< "\" failed: " << ktxErrorString(ktxresult);
throw std::runtime_error(message.str());
}
if (ktxTexture_NeedsTranscoding(kTexture)) {
TextureTranscoder tc;
tc.transcode((ktxTexture2*)kTexture);
//transcoded = true;
}
ktxresult = ktxTexture_GLUpload(kTexture,
&gnCubemapTexture, &cubemapTexTarget,
&glerror);
if (KTX_SUCCESS != ktxresult) {
std::stringstream message;
message << "ktxTexture_GLUpload failed: ";
if (ktxresult != KTX_GL_ERROR) {
message << ktxErrorString(ktxresult);
throw std::runtime_error(message.str());
} else if (kTexture->isCompressed && glerror == GL_INVALID_ENUM) {
throw unsupported_ctype();
} else {
message << std::showbase << "GL error " << std::hex << glerror
<< " occurred.";
throw std::runtime_error(message.str());
}
}
if (cubemapTexTarget != GL_TEXTURE_CUBE_MAP) {
std::stringstream message;
message << "Loaded texture is not a cubemap texture.";
throw std::runtime_error(message.str());
}
numLayers = kTexture->numLayers;
if (numLayers > 1 || kTexture->generateMipmaps)
// GLUpload will have generated the mipmaps already.
bIsMipmapped = true;
else
bIsMipmapped = false;
levelCount = kTexture->numLevels;
if (kTexture->orientation.y == KTX_ORIENT_Y_DOWN) {
// Assume a KTX-compliant cube map. That means the faces are in a
// LH coord system with +y up, +z forward and +x on the right.
// Scale the skybox cube's z by -1 to convert it to LH coords to
// match the cube map while placing the +z face in the -z direction
// so it will be in front of the view. Alternatively we could multiply
// the cube's x by -1 which will place the +z face in the +z direction
// placing it behind the viewer.
ubo.uvwTransform = glm::scale(glm::mat4(1.0f), glm::vec3(1, 1, -1));
} else {
std::stringstream message;
message << "Cubemap faces have unsupported KTXorientation value.";
throw std::runtime_error(message.str());
}
ktxTexture_Destroy(kTexture);
try {
prepare();
} catch (std::exception& e) {
(void)e; // To quiet unused variable warnings from some compilers.
cleanup();
throw;
}
bInitialized = true;
}
TextureCubemap::~TextureCubemap()
{
cleanup();
}
int
TextureCubemap::doEvent(SDL_Event* event)
{
int result = 0;
switch(event->type) {
case SDL_EVENT_USER:
if (event->user.code == SwipeDetector::swipeGesture) {
SwipeDetector::Direction direction
= SwipeDetector::pointerToDirection(event->user.data1);
switch (direction) {
case SwipeDetector::Direction::up:
toggleObject(+1);
break;
case SwipeDetector::Direction::down:
toggleObject(-1);
break;
default:
result = 1;
}
} else {
result = 1;
}
break;
default:
result = 1;
}
if (result == 1)
result = GL3LoadTestSample::doEvent(event);
return result;
}
void
TextureCubemap::resize(uint32_t width, uint32_t height)
{
this->w_width = width;
this->w_height = height;
glViewport(0, 0, width, height);
updateUniformBuffers();
}
void
TextureCubemap::run(uint32_t /*msTicks*/)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Draw object.
glFrontFace(GL_CW); // Why is everything CW?
glCullFace(GL_BACK);
glUseProgram(gnReflectProg);
meshes.objects[meshes.objectIndex].Draw();
if (bDisplaySkybox) {
// Change so depth test passes when values are equal to the
// depth buffer's content. This works in conjunction with the
// gl_Position = inPos.xyww trick in the shader.
glDepthFunc(GL_LEQUAL);
// The cube is a regular mesh with the front faces on the outside.
// We're inside the cube so want to see the back faces.
glCullFace(GL_FRONT);
glUseProgram(gnSkyboxProg);
meshes.skybox.Draw();
// Revert to defaults for 3D object.
glDepthFunc(GL_LESS);
}
assert(GL_NO_ERROR == glGetError());
}
//===================================================================
void
TextureCubemap::processArgs(std::string sArgs)
{
// Options descriptor
struct argparser::option longopts[] = {
{"external", argparser::option::no_argument, &externalFile, 1},
{NULL, argparser::option::no_argument, NULL, 0}
};
argvector argv(sArgs);
argparser ap(argv);
int ch;
while ((ch = ap.getopt(nullptr, longopts, nullptr)) != -1) {
switch (ch) {
case 0: break;
default: assert(false); // Error in args in sample table.
}
}
assert(ap.optind < argv.size());
ktxfilename = argv[ap.optind];
}
/* ------------------------------------------------------------------------- */
void
TextureCubemap::cleanup()
{
glEnable(GL_DITHER);
glDisable(GL_CULL_FACE);
glCullFace(GL_BACK);
glFrontFace(GL_CCW);
glDisable(GL_DEPTH_TEST);
if (bInitialized) {
glUseProgram(0);
glDeleteTextures(1, &gnCubemapTexture);
glDeleteProgram(gnReflectProg);
glDeleteProgram(gnSkyboxProg);
meshes.skybox.FreeGLResources();
for (int i = 0; i < 3; i++) {
meshes.objects[i].FreeGLResources();
}
}
assert(GL_NO_ERROR == glGetError());
}
void
TextureCubemap::loadMeshes()
{
std::string filepath = getAssetPath();
// Skybox
loadMesh(filepath + "cube.obj", meshes.skybox, vertexLayout, 0.05f);
// Objects
meshes.objects.resize(3);
loadMesh(filepath + "sphere.obj", meshes.objects[0], vertexLayout, 0.05f);
loadMesh(filepath + "teapot.dae", meshes.objects[1], vertexLayout, 0.05f);
loadMesh(filepath + "torusknot.obj", meshes.objects[2], vertexLayout, 0.05f);
}
void
TextureCubemap::prepareUniformBuffers()
{
uReflectProgramUniforms = glGetUniformBlockIndex(gnReflectProg, "UBO");
if (uReflectProgramUniforms == -1) {
std::stringstream message;
message << "prepareUniformBuffers: UBO not found in reflect program";
throw std::runtime_error(message.str());
}
uSkyboxProgramUniforms = glGetUniformBlockIndex(gnSkyboxProg, "UBO");
if (uSkyboxProgramUniforms == -1) {
std::stringstream message;
message << "prepareUniformBuffers: UBO not found in skybox program";
throw std::runtime_error(message.str());
}
glGenBuffers(1, &gnUbo);
glBindBuffer(GL_UNIFORM_BUFFER, gnUbo);
// Create the data store.
glBufferData(GL_UNIFORM_BUFFER, sizeof(ubo), 0, GL_DYNAMIC_DRAW);
glBindBufferBase(GL_UNIFORM_BUFFER, uniformBufferBindId, gnUbo);
glUseProgram(gnReflectProg);
glUniformBlockBinding(gnReflectProg, uReflectProgramUniforms,
uniformBufferBindId);
glUseProgram(gnSkyboxProg);
glUniformBlockBinding(gnSkyboxProg, uSkyboxProgramUniforms,
uniformBufferBindId);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
updateUniformBuffers();
glUseProgram(0);
assert(glGetError() == GL_NO_ERROR);
}
void
TextureCubemap::updateUniformBuffers()
{
// Reflect / 3D object
glm::mat4 viewMatrix = glm::mat4(1.0f);
ubo.projection = glm::perspective(glm::radians(60.0f),
(float)w_width / (float)w_height,
0.001f, 256.0f);
viewMatrix = glm::translate(viewMatrix, glm::vec3(0.0f, 0.0f, zoom));
// Transform the z axis to what the viewer is perceiving as the z axis to make
// the behaviour of 2-finger rotation (the value in rotation.z) understandable.
glm::mat4 zAxisRotMatrix;
zAxisRotMatrix = glm::rotate(zAxisRotMatrix, glm::radians(180.0f - rotation.x),
glm::vec3(1.0f, 0.0f, 0.0f));
zAxisRotMatrix = glm::rotate(zAxisRotMatrix, glm::radians(rotation.y),
glm::vec3(0.0f, 1.0f, 0.0f));
zRotationAxis = normalize(zAxisRotMatrix * glm::vec4(0.0f, 0.0f, 1.0f, 1.0f));
// I do not understand why this is necessary. Assimp is supposed to
// put models in the GL coordinate system by default but the teapot is
// upside down. Since the other objects are symmetrical it is not possible
// to say if they are upside down.
glm::mat4 object;
object = glm::rotate(object, glm::radians(180.0f),
glm::vec3(1.0f, 0.0f, 0.0f));
ubo.modelView = viewMatrix * glm::translate(glm::mat4(), cameraPos);
ubo.modelView = glm::rotate(ubo.modelView, glm::radians(rotation.x),
glm::vec3(1.0f, 0.0f, 0.0f));
ubo.modelView = glm::rotate(ubo.modelView, glm::radians(rotation.y),
glm::vec3(0.0f, 1.0f, 0.0f));
ubo.modelView = glm::rotate(ubo.modelView, glm::radians(rotation.z),
zRotationAxis);
// Remove translation from modelView so the skybox doesn't move.
ubo.skyboxView = glm::mat4(glm::mat3(ubo.modelView));
// Do the inverse here because doing it in every fragment is a bit much.
ubo.invModelView = glm::inverse(ubo.modelView);
// Now add the object rotation.
ubo.modelView = ubo.modelView * object;
glBindBuffer(GL_UNIFORM_BUFFER, gnUbo);
#if !defined(EMSCRIPTEN)
uint8_t* pData = (uint8_t*)glMapBufferRange(GL_UNIFORM_BUFFER,
0, sizeof(ubo),
GL_MAP_WRITE_BIT);
memcpy(pData, &ubo, sizeof(ubo));
glUnmapBuffer(GL_UNIFORM_BUFFER);
#else
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(ubo), &ubo);
#endif
glBindBuffer(GL_UNIFORM_BUFFER, 0);
}
void
TextureCubemap::prepareSampler()
{
glBindTexture(cubemapTexTarget, gnCubemapTexture);
if (bIsMipmapped)
glTexParameteri(cubemapTexTarget,
GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
else
glTexParameteri(cubemapTexTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(cubemapTexTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glUseProgram(gnReflectProg);
if ((uReflectCubemap = glGetUniformLocation(gnReflectProg,
"uSamplerColor")) == -1) {
std::stringstream message;
message << "prepareSampler: uSamplerColor not found in reflect program";
throw std::runtime_error(message.str());
}
glUniform1i(uReflectCubemap, cubemapTexUnit - GL_TEXTURE0);
glUseProgram(gnSkyboxProg);
if ((uSkyboxCubemap = glGetUniformLocation(gnSkyboxProg,
"uSamplerColor")) == -1) {
std::stringstream message;
message << "prepareSampler: uSamplerColor not found in skybox program";
throw std::runtime_error(message.str());
}
glUniform1i(uSkyboxCubemap, cubemapTexUnit - GL_TEXTURE0);
glUseProgram(0);
}
void
TextureCubemap::preparePrograms()
{
GLuint gnReflectFs, gnReflectVs, gnSkyboxFs, gnSkyboxVs;
const GLchar* actualReflectFs;
const GLchar* actualSkyboxFs;
if (framebufferColorEncoding() == GL_LINEAR) {
actualReflectFs = pszReflectSrgbEncodeFs;
actualSkyboxFs = pszSkyboxSrgbEncodeFs;
} else {
actualReflectFs = pszReflectFs;
actualSkyboxFs = pszSkyboxFs;
}
try {
makeShader(GL_VERTEX_SHADER, pszReflectVs, &gnReflectVs);
makeShader(GL_FRAGMENT_SHADER, actualReflectFs, &gnReflectFs);
makeProgram(gnReflectVs, gnReflectFs, &gnReflectProg);
makeShader(GL_VERTEX_SHADER, pszSkyboxVs, &gnSkyboxVs);
makeShader(GL_FRAGMENT_SHADER, actualSkyboxFs, &gnSkyboxFs);
makeProgram(gnSkyboxVs, gnSkyboxFs, &gnSkyboxProg);
} catch (std::exception& e) {
(void)e; // To quiet unused variable warnings from some compilers.
throw;
}
glDeleteShader(gnReflectVs);
glDeleteShader(gnReflectFs);
glDeleteShader(gnSkyboxVs);
glDeleteShader(gnSkyboxFs);
}
void
TextureCubemap::prepare()
{
// By default dithering is enabled. Dithering does not provide visual
// improvement in this sample so disable it to improve performance.
glDisable(GL_DITHER);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
glClearColor(0.2f,0.3f,0.4f,1.0f);
loadMeshes();
preparePrograms();
prepareUniformBuffers();
prepareSampler();
}
void
TextureCubemap::toggleSkyBox()
{
bDisplaySkybox = !bDisplaySkybox;
}
void
TextureCubemap::toggleObject(int direction)
{
meshes.objectIndex += direction;
if (meshes.objectIndex >= static_cast<int32_t>(meshes.objects.size())) {
meshes.objectIndex = 0;
} else if (meshes.objectIndex < 0) {
meshes.objectIndex = static_cast<int32_t>(meshes.objects.size()) - 1;
}
}
void
TextureCubemap::changeLodBias(float delta)
{
ubo.lodBias += delta;
if (ubo.lodBias < 0.0f)
{
ubo.lodBias = 0.0f;
}
if (ubo.lodBias > levelCount)
{
ubo.lodBias = (float)levelCount;
}
updateUniformBuffers();
}
void
TextureCubemap::keyPressed(uint32_t keyCode)
{
switch (keyCode)
{
case 's':
toggleSkyBox();
break;
case ' ':
toggleObject(+1);
break;
case SDLK_KP_PLUS:
changeLodBias(0.1f);
break;
case SDLK_KP_MINUS:
changeLodBias(-0.1f);
break;
}
}
@@ -0,0 +1,114 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief Test loading of a cubemap.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#include <vector>
#include "GL3LoadTestSample.h"
#include <glm/gtc/matrix_transform.hpp>
#define VERTEX_BUFFER_BIND_ID 0
#define ENABLE_VALIDATION false
class TextureCubemap : public GL3LoadTestSample
{
public:
TextureCubemap(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath);
~TextureCubemap();
virtual int doEvent(SDL_Event* event);
virtual void resize(uint32_t width, uint32_t height);
virtual void run(uint32_t msTicks);
//virtual void getOverlayText(VulkanTextOverlay *textOverlay);
static LoadTestSample*
create(uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath);
protected:
const GLuint cubemapTexUnit;
const GLuint uniformBufferBindId;
GLuint levelCount = 0;
GLenum cubemapTexTarget;
GLuint gnCubemapTexture;
GLuint gnReflectProg;
GLuint gnSkyboxProg;
GLuint gnUbo;
bool bInitialized;
bool bIsMipmapped;
bool bDisplaySkybox = true;
uint32_t numLayers;
// Vertex layout for this example
struct TAVertex {
float pos[3];
float uv[2];
};
struct {
glMeshLoader::MeshBuffer skybox;
std::vector<glMeshLoader::MeshBuffer> objects;
int32_t objectIndex = 0;
} meshes;
struct {
// Global matrices
glm::mat4 projection;
glm::mat4 modelView;
glm::mat4 skyboxView;
glm::mat4 invModelView;
glm::mat4 uvwTransform;
float lodBias = 0.0f;
} ubo;
GLint uReflectProgramUniforms;
GLint uSkyboxProgramUniforms;
GLint uReflectCubemap;
GLint uSkyboxCubemap;
glm::vec3 zRotationAxis;
void cleanup();
void loadMeshes();
void prepareUniformBuffers();
void updateUniformBuffers();
void prepareSampler();
void preparePrograms();
void prepare();
void toggleSkyBox();
void toggleObject(int direction);
void changeLodBias(float delta);
void processArgs(std::string sArgs);
virtual void keyPressed(uint32_t keyCode);
virtual void viewChanged()
{
updateUniformBuffers();
}
GLuint skyboxVAO, skyboxVBO;
};
@@ -0,0 +1,125 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2018-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief Definition of test sample for loading and displaying all the levels of a 2D mipmapped texture.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <algorithm>
#include <time.h>
#include <sstream>
#include <vector>
#include <ktx.h>
#include "argparser.h"
#include "TextureMipmap.h"
#include "ltexceptions.h"
#define member_size(type, member) sizeof(((type *)0)->member)
const GLchar* pszLodFsDeclarations =
"precision mediump float;\n"
"in vec2 UV;\n"
"flat in float lambda;\n\n"
"layout (location = 0) out vec4 outFragColor;\n\n"
"uniform mediump sampler2D uSampler;\n\n";
const GLchar* pszLodFsMain =
"void main()\n"
"{\n"
" outFragColor = textureLod(uSampler, UV, lambda);\n"
"}";
const GLchar* pszLodSrgbEncodeFsMain =
"void main()\n"
"{\n"
" vec4 t_color = textureLod(uSampler, UV, lambda);\n"
" outFragColor.rgb = srgb_encode(t_color.rgb);\n"
" outFragColor.a = t_color.a;\n"
"}";
const GLchar* pszLodVsMain =
"out vec2 UV;\n"
"flat out float lambda;\n\n"
"void main()\n"
"{\n"
" UV = inUV;\n"
" lambda = gl_InstanceID + 0.5;\n"
" mat4 modelView = ubo.view * ubo.instance[gl_InstanceID].model;\n"
" gl_Position = ubo.projection * modelView * inPos;\n"
"}";
/* ------------------------------------------------------------------------- */
LoadTestSample*
TextureMipmap::create(uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath)
{
return new TextureMipmap(width, height, szArgs, sBasePath);
}
/**
* @internal
* @class TextureMipmap
* @~English
*
* @brief Test loading of 2D texture arrays.
*/
TextureMipmap::TextureMipmap(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath)
: InstancedSampleBase(width, height, szArgs, sBasePath)
{
zoom = -15.0f;
if (texTarget != GL_TEXTURE_2D || textureInfo.numLevels == 1) {
std::stringstream message;
message << "TextureMipmap requires a 2D mipmapped texture.";
throw std::runtime_error(message.str());
}
// Checking if KVData contains keys of interest would go here.
instanceCount = textureInfo.numLevels;
InstancedSampleBase::ShaderSource fs;
InstancedSampleBase::ShaderSource vs;
fs.push_back(pszLodFsDeclarations);
if (framebufferColorEncoding() == GL_LINEAR) {
fs.push_back(pszSrgbEncodeFunc);
fs.push_back(pszLodSrgbEncodeFsMain);
} else {
fs.push_back(pszLodFsMain);;
}
vs.push_back(pszInstancingVsDeclarations);
vs.push_back(pszLodVsMain);
try {
prepare(fs, vs);
} catch (std::exception& e) {
(void)e; // To quiet unused variable warnings from some compilers.
cleanup();
throw;
}
bInitialized = true;
}
@@ -0,0 +1,41 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2021 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief Declaration of test sample for loading and displaying all the levels of a 2D mipmapped texture.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#include <vector>
#include "InstancedSampleBase.h"
#include <glm/gtc/matrix_transform.hpp>
#define VERTEX_BUFFER_BIND_ID 0
#define ENABLE_VALIDATION false
class TextureMipmap : public InstancedSampleBase
{
public:
TextureMipmap(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath);
static LoadTestSample*
create(uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath);
protected:
using ShaderSource = GL3LoadTestSample::ShaderSource;
};
@@ -0,0 +1,245 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 sts expandtab: */
/*
* Copyright 2018-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief Draw a textured cube.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#if defined(_WIN32)
#if _MSC_VER < 1900
#define snprintf _snprintf
#endif
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <assert.h>
#include <sstream>
#include <ktx.h>
#include "disable_glm_warnings.h"
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include "reenable_warnings.h"
#include "TexturedCube.h"
#include "cube.h"
/* ------------------------------------------------------------------------- */
extern const GLchar* pszVs;
extern const GLchar *pszDecalFs, *pszDecalSrgbEncodeFs;
/* ------------------------------------------------------------------------- */
LoadTestSample*
TexturedCube::create(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath)
{
return new TexturedCube(width, height, szArgs, sBasePath);
}
TexturedCube::TexturedCube(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath)
: GL3LoadTestSample(width, height, szArgs, sBasePath)
{
std::string filename;
GLenum target;
GLenum glerror;
GLuint gnDecalFs, gnVs;
GLsizeiptr offset;
ktxTexture* kTexture;
KTX_error_code ktxresult;
bInitialized = GL_FALSE;
gnTexture = 0;
filename = getAssetPath() + szArgs;
ktxresult = ktxTexture_CreateFromNamedFile(filename.c_str(),
KTX_TEXTURE_CREATE_NO_FLAGS,
&kTexture);
if (KTX_SUCCESS != ktxresult) {
std::stringstream message;
message << "Creation of ktxTexture from \"" << filename
<< "\" failed: " << ktxErrorString(ktxresult);
throw std::runtime_error(message.str());
}
ktxresult = ktxTexture_GLUpload(kTexture, &gnTexture, &target, &glerror);
if (KTX_SUCCESS == ktxresult) {
if (target != GL_TEXTURE_2D) {
/* Can only draw 2D textures */
std::stringstream message;
glDeleteTextures(1, &gnTexture);
message << "App can only draw 2D textures.";
throw std::runtime_error(message.str());
}
if (kTexture->numLevels > 1)
// Enable bilinear mipmapping.
// TO DO: application can consider inserting a key,value pair in
// the KTX file that indicates what type of filtering to use.
glTexParameteri(target,
GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
else
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
ktxTexture_Destroy(kTexture);
assert(GL_NO_ERROR == glGetError());
} else {
std::stringstream message;
message << "Load of texture from \"" << filename << "\" failed: ";
if (ktxresult == KTX_GL_ERROR) {
message << std::showbase << "GL error " << std::hex << glerror
<< " occurred.";
} else {
message << ktxErrorString(ktxresult);
}
throw std::runtime_error(message.str());
}
// By default dithering is enabled. Dithering does not provide visual
// improvement in this sample so disable it to improve performance.
glDisable(GL_DITHER);
glEnable(GL_CULL_FACE);
glClearColor(0.2f,0.3f,0.4f,1.0f);
// Create a VAO and bind it.
glGenVertexArrays(1, &gnVao);
glBindVertexArray(gnVao);
// Must have vertex data in buffer objects to use VAO's on ES3/GL Core
glGenBuffers(2, gnVbo);
glBindBuffer(GL_ARRAY_BUFFER, gnVbo[0]);
// Must be done after the VAO is bound
// WebGL requires different buffers for data and indices.
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gnVbo[1]);
// Create the buffer data store.
glBufferData(GL_ARRAY_BUFFER,
sizeof(cube_face) + sizeof(cube_color) + sizeof(cube_texture)
+ sizeof(cube_normal), NULL, GL_STATIC_DRAW);
// Interleave data copying and attrib pointer setup so offset is only
// computed once.
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glEnableVertexAttribArray(3);
offset = 0;
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(cube_face), cube_face);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid*)offset);
offset += sizeof(cube_face);
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(cube_color), cube_color);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid*)offset);
offset += sizeof(cube_color);
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(cube_texture), cube_texture);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid*)offset);
offset += sizeof(cube_texture);
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(cube_normal), cube_normal);
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid*)offset);
offset += sizeof(cube_normal);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(cube_index_buffer),
cube_index_buffer, GL_STATIC_DRAW);
const GLchar* actualDecalFs;
if (framebufferColorEncoding() == GL_LINEAR) {
actualDecalFs = pszDecalSrgbEncodeFs;
} else {
actualDecalFs = pszDecalFs;
}
try {
makeShader(GL_VERTEX_SHADER, pszVs, &gnVs);
makeShader(GL_FRAGMENT_SHADER, actualDecalFs, &gnDecalFs);
makeProgram(gnVs, gnDecalFs, &gnTexProg);
} catch (std::exception& e) {
(void)e; // To quiet unused variable warnings from some compilers.
throw;
}
try {
makeShader(GL_VERTEX_SHADER, pszVs, &gnVs);
makeShader(GL_FRAGMENT_SHADER, pszDecalFs, &gnDecalFs);
makeProgram(gnVs, gnDecalFs, &gnTexProg);
} catch (std::exception& e) {
(void)e; // To quiet unused variable warnings from some compilers.
throw;
}
gulMvMatrixLocTP = glGetUniformLocation(gnTexProg, "mvmatrix");
gulPMatrixLocTP = glGetUniformLocation(gnTexProg, "pmatrix");
gulSamplerLocTP = glGetUniformLocation(gnTexProg, "sampler");
glUseProgram(gnTexProg);
// We're using the default texture unit 0
glUniform1i(gulSamplerLocTP, 0);
glDeleteShader(gnVs);
glDeleteShader(gnDecalFs);
assert(GL_NO_ERROR == glGetError());
bInitialized = GL_TRUE;
}
TexturedCube::~TexturedCube()
{
glEnable(GL_DITHER);
glEnable(GL_CULL_FACE);
if (bInitialized) {
glUseProgram(0);
glDeleteTextures(1, &gnTexture);
glDeleteProgram(gnTexProg);
glDeleteBuffers(2, gnVbo);
glDeleteVertexArrays(1, &gnVao);
}
assert(GL_NO_ERROR == glGetError());
}
void
TexturedCube::resize(uint32_t uWidth, uint32_t uHeight)
{
glm::mat4 matProj;
glViewport(0, 0, uWidth, uHeight);
matProj = glm::perspective(glm::radians(45.f),
uWidth / (float)uHeight,
1.f, 100.f);
glUniformMatrix4fv(gulPMatrixLocTP, 1, GL_FALSE, glm::value_ptr(matProj));
}
void
TexturedCube::run(uint32_t msTicks)
{
// Setup the view matrix : just turn around the cube.
const float fDistance = 5.0f;
glm::vec3 eye((float)cos( msTicks*0.001f ) * fDistance,
(float)sin( msTicks*0.0007f ) * fDistance,
(float)sin( msTicks*0.001f ) * fDistance);
glm::vec3 look(0.,0.,0.);
glm::vec3 up(0.,1.,0.);
glm::mat4 matView = glm::lookAt( eye, look, up );
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUniformMatrix4fv(gulMvMatrixLocTP, 1, GL_FALSE, glm::value_ptr(matView));
glDrawElements(GL_TRIANGLES, CUBE_NUM_INDICES, GL_UNSIGNED_SHORT, 0);
assert(GL_NO_ERROR == glGetError());
}
/* ------------------------------------------------------------------------- */
@@ -0,0 +1,51 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2018-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief GLLoadTestSample derived class for drawing a textured cube.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#ifndef TEXTURED_CUBE_H
#define TEXTURED_CUBE_H
#include "GL3LoadTestSample.h"
class TexturedCube : public GL3LoadTestSample {
public:
TexturedCube(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath);
~TexturedCube();
virtual void resize(uint32_t width, uint32_t height);
virtual void run(uint32_t msTicks);
//virtual void getOverlayText(GLTextOverlay *textOverlay);
static LoadTestSample*
create(uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath);
protected:
GLuint gnTexture;
GLuint gnTexProg;
GLuint gnVao;
GLuint gnVbo[2];
GLint gulMvMatrixLocTP;
GLint gulPMatrixLocTP;
GLint gulSamplerLocTP;
bool bInitialized;
};
#endif /* TEXTURED_CUBE_H */
@@ -0,0 +1,81 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2016-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief Include appropriate version of gl.h for the shader-based tests.
*
* This is a separate header to avoid repetition of these conditionals.
*/
#ifndef MYGL_H
#define MYGL_H
#include <SDL3/SDL_video.h> // For the SDL_GL_CONTEXT_PROFILE macros
#if GL_CONTEXT_PROFILE == SDL_GL_CONTEXT_PROFILE_CORE
#ifdef _WIN32
#include <windows.h>
#undef KTX_USE_GETPROC /* Must use GETPROC on Windows */
#define KTX_USE_GETPROC 1
#else
#if !defined(KTX_USE_GETPROC)
#define KTX_USE_GETPROC 0
#endif
#endif
#if KTX_USE_GETPROC
#include <GL/glew.h>
#else
#define GL_GLEXT_PROTOTYPES
#include <GL/glcorearb.h>
#endif
//#define GL_APIENTRY APIENTRY
#elif GL_CONTEXT_PROFILE == SDL_GL_CONTEXT_PROFILE_COMPATIBILITY
#error This application is not intended to run in compatibility mode.
#elif GL_CONTEXT_PROFILE == SDL_GL_CONTEXT_PROFILE_ES
#if GL_CONTEXT_MAJOR_VERSION == 1
#error This application cannot run on OpenGL ES 1.
#elif GL_CONTEXT_MAJOR_VERSION == 2
#define GL_GLEXT_PROTOTYPES
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#elif GL_CONTEXT_MAJOR_VERSION == 3
#define GL_GLEXT_PROTOTYPES
#include <GLES3/gl3.h>
#include <GLES2/gl2ext.h>
#else
#error Unexpected GL_CONTEXT_MAJOR_VERSION
#endif
#endif
/* To help find supported transcode targets */
#if !defined(GL_ETC1_RGB8_OES)
#define GL_ETC1_RGB8_OES 0x8D64
#endif
#if !defined(GL_COMPRESSED_RGB8_ETC2)
#define GL_COMPRESSED_RGB8_ETC2 0x9274
#endif
#if !defined(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT)
#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3
#endif
#endif /* MYGL_H */
@@ -0,0 +1,115 @@
/* -*- tab-iWidth: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2008 HI Corporation.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief Shaders used by the DrawTexture and TexturedCube samples.
*/
#include "GL/glcorearb.h"
#include "ktx.h"
const GLchar* pszVs =
"layout(location = 0) in vec4 position;\n"
"layout(location = 1) in vec4 color;\n"
"layout(location = 2) in vec4 texcoord;\n\n"
"out vec4 v_color;\n"
"out vec2 v_texcoord;\n\n"
"uniform highp mat4 mvmatrix;\n"
"uniform highp mat4 pmatrix;\n\n"
"void main(void)\n"
"{\n"
" mat4 mvpmatrix = pmatrix * mvmatrix;\n"
" v_color = color;\n"
" v_texcoord = texcoord.xy;\n"
" gl_Position = mvpmatrix * position;\n"
"}";
const GLchar* pszDecalFs =
"precision mediump float;\n\n"
"uniform sampler2D sampler;\n\n"
"in vec4 v_color;\n"
"in vec2 v_texcoord;\n"
"out vec4 fragcolor;\n\n"
"void main(void)\n"
"{\n"
" vec4 color = texture(sampler, v_texcoord);\n"
" // DECAL\n"
" fragcolor.rgb = v_color.rgb * (1.0f - color.a) + color.rgb * color.a;\n"
" // No need for blending with page or screen background.\n"
" fragcolor.a = 1.0f;\n"
"}";
const GLchar* pszColorFs =
"precision mediump float;\n\n"
"in vec4 v_color;\n"
"out vec4 fragcolor;\n\n"
"void main(void)\n"
"{\n"
" fragcolor = v_color;\n"
"}";
// For use when sRGB rendering is not available. Note this is really a
// workaround for broken GL implementations (most) where "linear" framebuffers
// are really sRGB framebuffers but since they are "linear" GL does not encode
// the output of the fs to sRGB so we do it ourselves.
const GLchar* pszDecalSrgbEncodeFs =
"precision mediump float;\n\n"
"uniform sampler2D sampler;\n\n"
"in vec4 v_color;\n"
"in vec2 v_texcoord;\n"
"out vec4 fragcolor;\n\n"
"vec3 srgb_encode(vec3 color) {\n"
" float r = color.r < 0.0031308 ? 12.92 * color.r : 1.055 * pow(color.r, 1.0/2.4) - 0.055;\n"
" float g = color.g < 0.0031308 ? 12.92 * color.g : 1.055 * pow(color.g, 1.0/2.4) - 0.055;\n"
" float b = color.b < 0.0031308 ? 12.92 * color.b : 1.055 * pow(color.b, 1.0/2.4) - 0.055;\n"
" return vec3(r, g, b);\n"
"}\n\n"
"void main(void)\n"
"{\n"
" vec4 t_color = texture(sampler, v_texcoord);\n"
" vec3 lin_fragcolor;\n"
" // DECAL\n"
" lin_fragcolor = v_color.rgb * (1.0f - t_color.a) + t_color.rgb * t_color.a;\n"
" fragcolor.rgb = srgb_encode(lin_fragcolor);\n"
" // No need for blending with page or screen background.\n"
" fragcolor.a = 1.0f;\n"
"}";
const GLchar* pszColorSrgbEncodeFs =
"precision mediump float;\n\n"
"in vec4 v_color;\n"
"out vec4 fragcolor;\n\n"
"vec3 srgb_encode(vec3 color) {\n"
" float r = color.r < 0.0031308 ? 12.92 * color.r : 1.055 * pow(color.r, 1.0/2.4) - 0.055;\n"
" float g = color.g < 0.0031308 ? 12.92 * color.g : 1.055 * pow(color.g, 1.0/2.4) - 0.055;\n"
" float b = color.b < 0.0031308 ? 12.92 * color.b : 1.055 * pow(color.b, 1.0/2.4) - 0.055;\n"
" return vec3(r, g, b);\n"
"}\n\n"
"void main(void)\n"
"{\n"
" fragcolor.rgb = srgb_encode(v_color.rgb);\n"
" fragcolor.a = v_color.a;\n"
"}";
/* ----------------------------------------------------------------------------- */
@@ -0,0 +1,516 @@
/*
* Copyright 2017-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdlib.h>
#include <string>
#include <fstream>
#include <iostream>
#include <sstream>
#include <map>
#include <stdexcept>
#include <vector>
#ifdef _WIN32
#include <windows.h>
#include <fcntl.h>
#include <io.h>
#else
#endif
// Emscripten assimp port not yet available.
#if !defined(__EMSCRIPTEN__)
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>
#endif
#include "disable_glm_warnings.h"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "reenable_warnings.h"
#if defined(__ANDROID__)
#include <android/asset_manager.h>
#endif
//#include <glad/glad.h>
namespace glMeshLoader
{
typedef enum VertexLayout {
VERTEX_LAYOUT_POSITION = 0x0,
VERTEX_LAYOUT_NORMAL = 0x1,
VERTEX_LAYOUT_COLOR = 0x2,
VERTEX_LAYOUT_UV = 0x3,
VERTEX_LAYOUT_TANGENT = 0x4,
VERTEX_LAYOUT_BITANGENT = 0x5,
VERTEX_LAYOUT_DUMMY_FLOAT = 0x6,
VERTEX_LAYOUT_DUMMY_VEC4 = 0x7
} VertexLayout;
struct MeshBufferInfo
{
GLuint name = 0;
size_t size = 0;
};
struct MeshBuffer
{
GLuint vao = 0;
MeshBufferInfo vertices;
MeshBufferInfo indices;
GLuint primitiveType;
uint32_t indexCount = 0;
glm::vec3 dim;
glm::mat4 modelTransform; // To display the model correctly in the
// GL coordinate system.
void FreeGLResources() {
if (vertices.name)
glDeleteBuffers(1, &vertices.name);
if (indices.name)
glDeleteBuffers(1, &indices.name);
if (vao)
glDeleteVertexArrays(1, &vao);
}
~MeshBuffer() {
FreeGLResources();
}
glm::mat4& getModelTransform() { return modelTransform; }
void Draw() {
glBindVertexArray(vao);
glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
}
};
#if !defined(__EMSCRIPTEN__)
// Get vertex size from vertex layout
static uint32_t vertexSize(std::vector<glMeshLoader::VertexLayout> layout)
{
uint32_t vSize = 0;
for (auto& layoutDetail : layout)
{
switch (layoutDetail)
{
// UV only has two components
case VERTEX_LAYOUT_UV:
vSize += 2 * sizeof(float);
break;
default:
vSize += 3 * sizeof(float);
}
}
return vSize;
}
#endif
}
#if !defined(__EMSCRIPTEN__)
// Simple mesh class for getting all the necessary stuff from models
// loaded via ASSIMP.
class GLMeshLoader {
private:
struct Vertex
{
glm::vec3 m_pos;
glm::vec2 m_tex;
glm::vec3 m_normal;
glm::vec3 m_color;
glm::vec3 m_tangent;
glm::vec3 m_binormal;
Vertex() {}
Vertex(const glm::vec3& pos, const glm::vec2& tex, const glm::vec3& normal, const glm::vec3& tangent, const glm::vec3& bitangent, const glm::vec3& color)
{
m_pos = pos;
m_tex = tex;
m_normal = normal;
m_color = color;
m_tangent = tangent;
m_binormal = bitangent;
}
};
struct MeshEntry {
uint32_t NumIndices;
uint32_t MaterialIndex;
uint32_t vertexBase;
uint32_t primitiveType;
std::vector<Vertex> Vertices;
std::vector<unsigned int> Indices;
};
aiMatrix4x4 rootTransform;
public:
#if defined(__ANDROID__)
AAssetManager* assetManager = nullptr;
#endif
std::vector<MeshEntry> m_Entries;
struct Dimension
{
glm::vec3 min = glm::vec3(FLT_MAX);
glm::vec3 max = glm::vec3(-FLT_MAX);
glm::vec3 size;
} dim;
uint32_t numVertices = 0;
Assimp::Importer importer;
const aiScene* pScene;
~GLMeshLoader()
{
m_Entries.clear();
}
// Load a mesh with some default flags
void LoadMesh(const std::string& filename)
{
int flags = aiProcess_Triangulate | aiProcess_PreTransformVertices
| aiProcess_JoinIdenticalVertices
| aiProcess_CalcTangentSpace
| aiProcess_GenSmoothNormals;
LoadMesh(filename, flags);
}
// Load the mesh with custom flags
void LoadMesh(const std::string& filename, int flags)
{
#if defined(__ANDROID__)
// Meshes are stored inside the apk on Android (compressed)
// So they need to be loaded via the asset manager
AAsset* asset = AAssetManager_open(assetManager, filename.c_str(),
AASSET_MODE_STREAMING);
assert(asset);
size_t size = AAsset_getLength(asset);
assert(size > 0);
void *meshData = malloc(size);
AAsset_read(asset, meshData, size);
AAsset_close(asset);
pScene = importer.ReadFileFromMemory(meshData, size, flags);
free(meshData);
#else
pScene = importer.ReadFile(filename.c_str(), flags);
#endif
if(!pScene || pScene->mFlags & AI_SCENE_FLAGS_INCOMPLETE
|| !pScene->mRootNode)
{
std::stringstream message;
message << " Import via ASSIMP from\"" << filename << "\" failed. "
<< importer.GetErrorString() << std::endl;
throw std::runtime_error(message.str());
}
InitFromScene(pScene, filename);
#if 0
// retrieve the directory path of the filepath
directory = path.substr(0, path.find_last_of('/'));
// process ASSIMP's root node recursively
processNode(scene->mRootNode, scene);
#endif
rootTransform = pScene->mRootNode->mTransformation;
}
void InitFromScene(const aiScene* pSrcScene, const std::string& /*filename*/)
{
m_Entries.resize(pSrcScene->mNumMeshes);
// Counters
for (unsigned int i = 0; i < m_Entries.size(); i++)
{
m_Entries[i].vertexBase = numVertices;
numVertices += pSrcScene->mMeshes[i]->mNumVertices;
}
// Initialize the meshes in the scene one by one
for (unsigned int i = 0; i < m_Entries.size(); i++)
{
const aiMesh* paiMesh = pSrcScene->mMeshes[i];
InitMesh(i, paiMesh, pSrcScene);
}
}
void InitMesh(unsigned int index, const aiMesh* paiMesh, const aiScene* pSrcScene)
{
m_Entries[index].MaterialIndex = paiMesh->mMaterialIndex;
aiColor3D pColor(0.f, 0.f, 0.f);
pSrcScene->mMaterials[paiMesh->mMaterialIndex]->Get(AI_MATKEY_COLOR_DIFFUSE, pColor);
aiVector3D Zero3D(0.0f, 0.0f, 0.0f);
for (unsigned int i = 0; i < paiMesh->mNumVertices; i++) {
aiVector3D* pPos = &(paiMesh->mVertices[i]);
aiVector3D* pNormal = &(paiMesh->mNormals[i]);
aiVector3D *pTexCoord;
if (paiMesh->HasTextureCoords(0))
{
pTexCoord = &(paiMesh->mTextureCoords[0][i]);
}
else {
pTexCoord = &Zero3D;
}
aiVector3D* pTangent = (paiMesh->HasTangentsAndBitangents()) ?
&(paiMesh->mTangents[i]) : &Zero3D;
aiVector3D* pBiTangent = (paiMesh->HasTangentsAndBitangents()) ?
&(paiMesh->mBitangents[i]) : &Zero3D;
Vertex v(glm::vec3(pPos->x, -pPos->y, pPos->z),
glm::vec2(pTexCoord->x , pTexCoord->y),
glm::vec3(pNormal->x, pNormal->y, pNormal->z),
glm::vec3(pTangent->x, pTangent->y, pTangent->z),
glm::vec3(pBiTangent->x, pBiTangent->y, pBiTangent->z),
glm::vec3(pColor.r, pColor.g, pColor.b)
);
dim.max.x = fmax(pPos->x, dim.max.x);
dim.max.y = fmax(pPos->y, dim.max.y);
dim.max.z = fmax(pPos->z, dim.max.z);
dim.min.x = fmin(pPos->x, dim.min.x);
dim.min.y = fmin(pPos->y, dim.min.y);
dim.min.z = fmin(pPos->z, dim.min.z);
m_Entries[index].Vertices.push_back(v);
}
dim.size = dim.max - dim.min;
for (unsigned int i = 0; i < paiMesh->mNumFaces; i++)
{
const aiFace& Face = paiMesh->mFaces[i];
if (Face.mNumIndices != 3)
continue;
m_Entries[index].Indices.push_back(Face.mIndices[0]);
m_Entries[index].Indices.push_back(Face.mIndices[1]);
m_Entries[index].Indices.push_back(Face.mIndices[2]);
}
switch (paiMesh->mPrimitiveTypes) {
case aiPrimitiveType_POINT:
m_Entries[index].primitiveType = GL_POINTS ;
break;
case aiPrimitiveType_LINE:
m_Entries[index].primitiveType = GL_LINES;
break;
case aiPrimitiveType_TRIANGLE:
m_Entries[index].primitiveType = GL_TRIANGLES;
break;
default: assert(false); // Shouldn't happen because of triangulate.
}
}
void CreateBuffers(glMeshLoader::MeshBuffer& meshBuffer,
std::vector<glMeshLoader::VertexLayout> layout,
float scale)
{
std::vector<float> vertexBuffer;
for (uint32_t m = 0; m < m_Entries.size(); m++)
{
for (uint32_t i = 0; i < m_Entries[m].Vertices.size(); i++)
{
// Push vertex data depending on layout
for (auto& layoutDetail : layout)
{
// Position
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_POSITION)
{
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_pos.x * scale);
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_pos.y * scale);
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_pos.z * scale);
}
// Normal
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_NORMAL)
{
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_normal.x);
vertexBuffer.push_back(-m_Entries[m].Vertices[i].m_normal.y);
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_normal.z);
}
// Texture coordinates
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_UV)
{
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_tex.s);
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_tex.t);
}
// Color
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_COLOR)
{
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_color.r);
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_color.g);
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_color.b);
}
// Tangent
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_TANGENT)
{
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_tangent.x);
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_tangent.y);
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_tangent.z);
}
// Bitangent
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_BITANGENT)
{
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_binormal.x);
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_binormal.y);
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_binormal.z);
}
// Dummy layout components for padding
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_DUMMY_FLOAT)
{
vertexBuffer.push_back(0.0f);
}
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_DUMMY_VEC4)
{
vertexBuffer.push_back(0.0f);
vertexBuffer.push_back(0.0f);
vertexBuffer.push_back(0.0f);
vertexBuffer.push_back(0.0f);
}
}
}
}
meshBuffer.vertices.size = vertexBuffer.size() * sizeof(float);
dim.min *= scale;
dim.max *= scale;
dim.size *= scale;
meshBuffer.modelTransform[0][0] = rootTransform.a1;
meshBuffer.modelTransform[0][1] = rootTransform.b1;
meshBuffer.modelTransform[0][2] = rootTransform.c1;
meshBuffer.modelTransform[0][3] = rootTransform.d1;
meshBuffer.modelTransform[1][0] = rootTransform.a2;
meshBuffer.modelTransform[1][1] = rootTransform.b2;
meshBuffer.modelTransform[1][2] = rootTransform.c2;
meshBuffer.modelTransform[1][3] = rootTransform.d2;
meshBuffer.modelTransform[2][0] = rootTransform.a3;
meshBuffer.modelTransform[2][1] = rootTransform.b3;
meshBuffer.modelTransform[2][2] = rootTransform.c3;
meshBuffer.modelTransform[2][3] = rootTransform.d3;
meshBuffer.modelTransform[3][0] = rootTransform.a4;
meshBuffer.modelTransform[3][1] = rootTransform.b4;
meshBuffer.modelTransform[3][2] = rootTransform.c4;
meshBuffer.modelTransform[3][3] = rootTransform.d4;
std::vector<uint32_t> indexBuffer;
for (uint32_t m = 0; m < m_Entries.size(); m++)
{
uint32_t indexBase = (uint32_t)indexBuffer.size();
for (uint32_t i = 0; i < m_Entries[m].Indices.size(); i++)
{
indexBuffer.push_back(m_Entries[m].Indices[i] + indexBase);
}
}
meshBuffer.indices.size = indexBuffer.size() * sizeof(uint32_t);
meshBuffer.indexCount = (uint32_t)indexBuffer.size();
meshBuffer.primitiveType = m_Entries[0].primitiveType;
// Make the GL buffers
glGenVertexArrays(1, &meshBuffer.vao);
glBindVertexArray(meshBuffer.vao);
// Setup vertices.
glGenBuffers(1, &meshBuffer.vertices.name);
glBindBuffer(GL_ARRAY_BUFFER, meshBuffer.vertices.name);
// Create the buffer data store and upload the vertices.
glBufferData(GL_ARRAY_BUFFER, meshBuffer.vertices.size,
vertexBuffer.data(), GL_STATIC_DRAW);
GLuint attribNumber = 0;
GLsizeiptr attribOffset = 0;
GLsizei stride = vertexSize(layout);
for (auto& layoutDetail : layout)
{
// Position
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_POSITION) {
glEnableVertexAttribArray(attribNumber);
glVertexAttribPointer(attribNumber, 3, GL_FLOAT, GL_FALSE,
stride, (void *)attribOffset);
attribOffset += sizeof(float) * 3;
attribNumber++;
}
// Normal
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_NORMAL) {
glEnableVertexAttribArray(attribNumber);
glVertexAttribPointer(attribNumber, 3, GL_FLOAT, GL_FALSE,
stride, (void *)attribOffset);
attribOffset += sizeof(float) * 3;
attribNumber++;
}
// Texture coordinates
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_UV) {
glEnableVertexAttribArray(attribNumber);
glVertexAttribPointer(attribNumber, 2, GL_FLOAT, GL_FALSE,
stride, (void *)attribOffset);
attribOffset += sizeof(float) * 2;
attribNumber++;
}
// Color
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_COLOR) {
glEnableVertexAttribArray(attribNumber);
glVertexAttribPointer(attribNumber, 3, GL_FLOAT, GL_FALSE,
stride, (void *)attribOffset);
attribOffset += sizeof(float) * 3;
attribNumber++;
}
// Tangent
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_TANGENT) {
glEnableVertexAttribArray(attribNumber);
glVertexAttribPointer(attribNumber, 3, GL_FLOAT, GL_FALSE,
stride, (void *)attribOffset);
attribOffset += sizeof(float) * 3;
attribNumber++;
}
// Bitangent
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_BITANGENT) {
glEnableVertexAttribArray(attribNumber);
glVertexAttribPointer(attribNumber, 3, GL_FLOAT, GL_FALSE,
stride, (void *)attribOffset);
attribOffset += sizeof(float) * 3;
attribNumber++;
}
// Dummy layout components for padding
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_DUMMY_FLOAT) {
vertexBuffer.push_back(0.0f);
}
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_DUMMY_VEC4) {
vertexBuffer.push_back(0.0f);
vertexBuffer.push_back(0.0f);
vertexBuffer.push_back(0.0f);
vertexBuffer.push_back(0.0f);
}
}
// Setup indices
glGenBuffers(1, &meshBuffer.indices.name);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, meshBuffer.indices.name);
// Create the buffer data store and upload the elements.
glBufferData(GL_ELEMENT_ARRAY_BUFFER, meshBuffer.indices.size,
indexBuffer.data(), GL_STATIC_DRAW);
glBindVertexArray(0);
}
};
#endif // !defined(__EMSCRIPTEN__)
@@ -0,0 +1,153 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2017-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <ktx.h>
#if !defined(GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT)
#define GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT 0x8A54
#define GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG 0x8C01
#define GL_COMPRESSED_RGBA_PVRTC_2BPPV2_IMG 0x9137
#endif
#if !defined(GL_COMPRESSED_RG_RGTC2)
#define GL_COMPRESSED_RG_RGTC2 0x8DBD
#endif
#if !defined(GL_COMPRESSED_RGBA_BPTC_UNORM)
#define GL_COMPRESSED_RGBA_BPTC_UNORM 0x8E8C
#endif
#if !defined(GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT)
#define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT 0x8E8E
#endif
class TextureTranscoder {
public:
TextureTranscoder() {
determineCompressedTexFeatures(deviceFeatures);
if (deviceFeatures.astc_ldr)
defaultTf = KTX_TTF_ASTC_4x4_RGBA;
else if (deviceFeatures.bc3)
defaultTf = KTX_TTF_BC1_OR_3;
else if (deviceFeatures.etc2)
defaultTf = KTX_TTF_ETC; // Let transcoder decide RGB or RGBA
else if (deviceFeatures.pvrtc1)
defaultTf = KTX_TTF_PVRTC1_4_RGBA;
else if (deviceFeatures.etc1)
defaultTf = KTX_TTF_ETC1_RGB;
else {
std::stringstream message;
message << "OpenGL implementation does not support any available transcode target.";
throw std::runtime_error(message.str());
}
}
void transcode(ktxTexture2* kTexture,
ktx_transcode_fmt_e otf = KTX_TTF_NOSELECTION) {
KTX_error_code ktxresult;
ktx_transcode_fmt_e tf;
if (otf != KTX_TTF_NOSELECTION) {
tf = otf;
} else {
khr_df_model_e colorModel = ktxTexture2_GetColorModel_e(kTexture);
if (colorModel == KHR_DF_MODEL_UASTC && deviceFeatures.astc_ldr) {
tf = KTX_TTF_ASTC_4x4_RGBA;
} else if (colorModel == KHR_DF_MODEL_ETC1S && deviceFeatures.etc2) {
tf = KTX_TTF_ETC;
} else {
tf = defaultTf;
}
}
ktxresult = ktxTexture2_TranscodeBasis(kTexture, tf, 0);
if (KTX_SUCCESS != ktxresult) {
std::stringstream message;
message << "Transcoding of ktxTexture2 to "
<< ktxTranscodeFormatString(tf) << " failed: "
<< ktxErrorString(ktxresult);
throw std::runtime_error(message.str());
}
}
protected:
ktx_transcode_fmt_e defaultTf;
struct compressedTexFeatures {
bool astc_ldr;
bool astc_hdr;
bool bc6h;
bool bc7;
bool etc1;
bool etc2;
bool bc3;
bool pvrtc1;
bool pvrtc_srgb;
bool pvrtc2;
bool rgtc;
} deviceFeatures;
void determineCompressedTexFeatures(compressedTexFeatures& features) {
ktx_int32_t numCompressedFormats;
memset(&features, false, sizeof(features));
glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &numCompressedFormats);
GLint* formats = new GLint[numCompressedFormats];
glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, formats);
for (ktx_int32_t i = 0; i < numCompressedFormats; i++) {
if (formats[i] == GL_COMPRESSED_RGBA8_ETC2_EAC)
features.etc2 = true;
if (formats[i] == GL_ETC1_RGB8_OES)
features.etc1 = true;
if (formats[i] == GL_COMPRESSED_RGBA_S3TC_DXT5_EXT)
features.bc3 = true;
if (formats[i] == GL_COMPRESSED_RG_RGTC2)
features.rgtc = true;
if (formats[i] == GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT)
features.pvrtc_srgb = true;
if (formats[i] == GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG)
features.pvrtc1 = true;
if (formats[i] == GL_COMPRESSED_RGBA_PVRTC_2BPPV2_IMG)
features.pvrtc2 = true;
if (formats[i] == GL_COMPRESSED_RGBA_ASTC_4x4_KHR)
features.astc_ldr = true;
if (formats[i] == GL_COMPRESSED_RGBA_BPTC_UNORM)
features.bc7 = true;
if (formats[i] == GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT)
features.bc6h = true;
}
delete[] formats;
// Just in case COMPRESSED_TEXTURE_FORMATS didn't return anything.
// There is no ETC2 extension. It went into core in OpenGL ES 2.0.
// ARB_es_compatibility is not a good indicator. ETC2 could be supported
// by software decompression. Better to report unsupported.
if (!features.etc1 && SDL_GL_ExtensionSupported("GL_OES_compressed_ETC1_RGB8_texture"))
features.etc1 = true;;
if (!features.bc3 && SDL_GL_ExtensionSupported("GL_EXT_texture_compression_s3tc"))
features.bc3 = true;
if (!features.rgtc && SDL_GL_ExtensionSupported("GL_ARB_texture_compression_rgtc"))
features.rgtc = true;
if (!features.pvrtc1 && SDL_GL_ExtensionSupported("GL_IMG_texture_compression_pvrtc"))
features.pvrtc1 = true;
if (!features.pvrtc2 && SDL_GL_ExtensionSupported("GL_IMG_texture_compression_pvrtc2"))
features.pvrtc2 = true;
if (!features.pvrtc_srgb && SDL_GL_ExtensionSupported("GL_EXT_pvrtc_sRGB"))
features.pvrtc_srgb = true;
if (!(features.bc7 && features.bc6h) && SDL_GL_ExtensionSupported("GL_ARB_texture_compression_bptc"))
features.bc6h = features.bc7 = true;
if (!features.astc_ldr && SDL_GL_ExtensionSupported("GL_KHR_texture_compression_astc_ldr"))
features.astc_ldr = true;
// The only way to identify this support is the extension string.
// The format name is the same.
if (SDL_GL_ExtensionSupported("GL_KHR_texture_compression_astc_hdr"))
features.astc_hdr = true;
}
};
+428
View File
@@ -0,0 +1,428 @@
# Copyright 2020 Andreas Atteneder
# SPDX-License-Identifier: Apache-2.0
# Find Vulkan package
if(APPLE)
# N.B. FindVulkan needs the VULKAN_SDK environment variable set to find
# the iOS frameworks and to set Vulkan_SDK_Base, used later in this
# file. Therefore ensure to make that env. var. available to CMake and
# Xcode. Special care is needed to ensure it is available to the CMake
# and Xcode GUIs.
# set(CMAKE_FIND_DEBUG_MODE TRUE)
find_package( Vulkan REQUIRED COMPONENTS MoltenVK )
# set(CMAKE_FIND_DEBUG_MODE FALSE)
# Derive some other useful variables from those provided by find_package
if(APPLE_LOCKED_OS)
set( Vulkan_SHARE_VULKAN ${Vulkan_SDK_Base}/${CMAKE_SYSTEM_NAME}/share/vulkan )
else()
# Vulkan_LIBRARIES points to "libvulkan.dylib".
# Find the name of the actual dylib which includes the version no.
# readlink -f requires macOS >= 12.3!
execute_process(COMMAND readlink -f ${Vulkan_LIBRARIES}
OUTPUT_VARIABLE Vulkan_LIBRARY_REAL_PATH_NAME
OUTPUT_STRIP_TRAILING_WHITESPACE
)
cmake_path(GET
Vulkan_LIBRARY_REAL_PATH_NAME
FILENAME
Vulkan_LIBRARY_REAL_FILE_NAME
)
# Find the name that includes only the major version number.
execute_process(COMMAND readlink ${Vulkan_LIBRARIES}
OUTPUT_VARIABLE Vulkan_LIBRARY_SONAME_FILE_NAME
OUTPUT_STRIP_TRAILING_WHITESPACE
)
set( Vulkan_SHARE_VULKAN appfwSDL/VulkanAppSDL/mac/vulkan )
endif()
else()
find_package(Vulkan REQUIRED)
endif()
#cmake_print_variables(
# Vulkan_LIBRARIES
# Vulkan_LIBRARY_REAL_PATH_NAME
# Vulkan_LIBRARY_REAL_FILE_NAME
# Vulkan_LIBRARY_SONAME_FILE_NAME
#)
include(compile_shader.cmake)
set(SHADER_SOURCES "")
compile_shader(shader_textoverlay textoverlay appfwSDL/VulkanAppSDL/shaders shaders )
compile_shader(shader_cube cube vkloadtests/shaders/cube shaders )
compile_shader(shader_cubemap_reflect reflect vkloadtests/shaders/cubemap shaders )
compile_shader(shader_cubemap_skybox skybox vkloadtests/shaders/cubemap shaders )
compile_shader_list(shader_texture vkloadtests/shaders/texture shaders texture.vert texture1d.frag texture2d.frag)
compile_shader(shader_texture3d instancing3d vkloadtests/shaders/texture3d shaders )
compile_shader(shader_texturearray instancing vkloadtests/shaders/texturearray shaders )
compile_shader(shader_texturemipmap instancinglod vkloadtests/shaders/texturemipmap shaders )
add_custom_target(
spirv_shaders
DEPENDS
shader_textoverlay
shader_cube
shader_cubemap_reflect
shader_cubemap_skybox
shader_texture
shader_texture3d
shader_texturearray
shader_texturemipmap
)
set( VK_TEST_IMAGES
etc1s_Iron_Bars_001_normal.ktx2
uastc_Iron_Bars_001_normal.ktx2
ktx_document_uastc_rdo4_zstd5.ktx2
color_grid_uastc_zstd.ktx2
color_grid_zstd.ktx2
color_grid_uastc.ktx2
color_grid_basis.ktx2
kodim17_basis.ktx2
pattern_02_bc2.ktx2
ktx_document_basis.ktx2
rgba-mipmap-reference-basis.ktx2
3dtex_7_reference_u.ktx2
arraytex_7_mipmap_reference_u.ktx2
cubemap_goldengate_uastc_rdo4_zstd5_rd.ktx2
cubemap_yokohama_basis_rd.ktx2
skybox_zstd.ktx2
orient-down-metadata.ktx
orient-up-metadata.ktx
rgba-reference.ktx
etc2-rgb.ktx
etc2-rgba8.ktx
etc2-sRGB.ktx
etc2-sRGBa8.ktx
pattern_02_bc2.ktx
rgb-amg-reference.ktx
metalplate-amg-rgba8.ktx
not4_rgb888_srgb.ktx
texturearray_bc3_unorm.ktx
texturearray_astc_8x8_unorm.ktx
texturearray_etc2_unorm.ktx
)
list( TRANSFORM VK_TEST_IMAGES
PREPEND "${PROJECT_SOURCE_DIR}/tests/testimages/"
)
set( KTX_RESOURCES ${LOAD_TEST_COMMON_RESOURCE_FILES} ${VK_TEST_IMAGES} )
if(APPLE)
# Adding this directory to KTX_RESOURCES and ultimately vkloadtests's
# RESOURCE property causes the install command (later in this file) to
# raise an error at configuration time: "RESOURCE given directory". Use
# this instead to cause the files to be added to Resources in the bundle.
set_source_files_properties( ${Vulkan_SHARE_VULKAN}
PROPERTIES
MACOSX_PACKAGE_LOCATION Resources
)
endif()
add_executable( vkloadtests
${EXE_FLAG}
appfwSDL/VulkanAppSDL/VulkanAppSDL.cpp
appfwSDL/VulkanAppSDL/VulkanAppSDL.h
appfwSDL/VulkanAppSDL/vulkancheckres.h
appfwSDL/VulkanAppSDL/VulkanContext.cpp
appfwSDL/VulkanAppSDL/VulkanContext.h
appfwSDL/VulkanAppSDL/vulkandebug.cpp
appfwSDL/VulkanAppSDL/vulkandebug.h
appfwSDL/VulkanAppSDL/VulkanSwapchain.cpp
appfwSDL/VulkanAppSDL/VulkanSwapchain.h
appfwSDL/VulkanAppSDL/vulkantextoverlay.hpp
appfwSDL/VulkanAppSDL/vulkantools.cpp
appfwSDL/VulkanAppSDL/vulkantools.h
common/disable_glm_warnings.h
common/reenable_warnings.h
vkloadtests/InstancedSampleBase.cpp
vkloadtests/InstancedSampleBase.h
vkloadtests/Texture.cpp
vkloadtests/Texture.h
vkloadtests/Texture3d.cpp
vkloadtests/Texture3d.h
vkloadtests/TextureArray.cpp
vkloadtests/TextureArray.h
vkloadtests/TextureCubemap.cpp
vkloadtests/TextureCubemap.h
vkloadtests/TexturedCube.cpp
vkloadtests/TexturedCube.h
vkloadtests/TextureMipmap.cpp
vkloadtests/TextureMipmap.h
vkloadtests/utils/VulkanMeshLoader.hpp
vkloadtests/utils/VulkanTextureTranscoder.hpp
vkloadtests/VulkanLoadTests.cpp
vkloadtests/VulkanLoadTests.h
vkloadtests/VulkanLoadTestSample.cpp
vkloadtests/VulkanLoadTestSample.h
${LOAD_TEST_COMMON_RESOURCE_FILES}
${Vulkan_SHARE_VULKAN}
${SHADER_SOURCES}
${VK_TEST_IMAGES}
vkloadtests.cmake
)
set_code_sign(vkloadtests)
# If VulkanAppSDL is ever made into its own target change the target here.
target_compile_features(vkloadtests
PRIVATE
cxx_std_14
)
target_include_directories(vkloadtests
PRIVATE
SDL3::Headers
$<TARGET_PROPERTY:appfwSDL,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:ktx,INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:objUtil,INTERFACE_INCLUDE_DIRECTORIES>
appfwSDL/VulkanAppSDL
vkloadtests
vkloadtests/utils
)
target_include_directories(vkloadtests
SYSTEM PRIVATE
${PROJECT_SOURCE_DIR}/other_include
)
target_link_libraries(vkloadtests
ktx
objUtil
appfwSDL
)
set_target_properties(vkloadtests PROPERTIES
CXX_VISIBILITY_PRESET ${STATIC_APP_LIB_SYMBOL_VISIBILITY}
)
target_link_libraries(
vkloadtests
Vulkan::Vulkan
)
if(APPLE)
if(IOS)
set( INFO_PLIST_IN "${PROJECT_SOURCE_DIR}/tests/loadtests/vkloadtests/resources/ios/Info.plist.in" )
set( icon_launch_assets
${PROJECT_SOURCE_DIR}/icons/ios/CommonIcons.xcassets
vkloadtests/resources/ios/LaunchImages.xcassets
vkloadtests/resources/ios/LaunchScreen.storyboard
)
target_sources( vkloadtests
PRIVATE
${icon_launch_assets}
)
# Add to resources so they'll be copied to the bundle.
list( APPEND KTX_RESOURCES ${icon_launch_assets} )
target_link_libraries(
vkloadtests
${AudioToolbox_LIBRARY}
${AVFoundation_LIBRARY}
${CoreAudio_LIBRARY}
${CoreBluetooth_LIBRARY}
${CoreGraphics_LIBRARY}
${CoreMotion_LIBRARY}
${CoreHaptics_LIBRARY}
${Foundation_LIBRARY}
${GameController_LIBRARY}
${IOSurface_LIBRARY}
${Metal_LIBRARY}
${MOLTENVK_FRAMEWORK}
${OpenGLES_LIBRARY}
${QuartzCore_LIBRARY}
${UIKit_LIBRARY}
)
else()
set( INFO_PLIST_IN "${PROJECT_SOURCE_DIR}/tests/loadtests/vkloadtests/resources/mac/Info.plist.in" )
endif()
elseif(WIN32)
ensure_runtime_dependencies_windows(vkloadtests)
elseif(LINUX)
target_sources(
vkloadtests
PRIVATE
vkloadtests/resources/linux/vkloadtests.desktop
)
endif()
target_link_libraries( vkloadtests ${LOAD_TEST_COMMON_LIBS} )
target_compile_definitions(
vkloadtests
PRIVATE
$<TARGET_PROPERTY:ktx,INTERFACE_COMPILE_DEFINITIONS>
$<$<PLATFORM_ID:Windows>:NOMINMAX>
)
set_target_properties( vkloadtests PROPERTIES RESOURCE "${KTX_RESOURCES};${SHADER_SOURCES}" )
if(APPLE)
set( product_name vkloadtests )
set( bundle_identifier org.khronos.ktx.${product_name} )
# This property must be set to avoid an Xcode warning.
set_target_properties( vkloadtests PROPERTIES XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER ${bundle_identifier} )
# The file identified by MACOSX_BUNDLE_INFO_PLIST is subject to an
# implicit configure_file() step by CMake. Since this target has a custom
# Info.plist this is not strictly necessary but the writer does not know
# how to prevent it. Furthermore the BUNDLE_NAME, EXECUTABLE_NAME and
# GUI_IDENTIFIER properties could all be set from Xcode build settings
# but using those in the custom Info.plist would not be portable to other
# generators. Since configure_file() is happening use the standard
# property names for consistency with the standard Info.plist template.
set_target_properties( vkloadtests PROPERTIES
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_BUNDLE_NAME ${product_name}
MACOSX_BUNDLE_EXECUTABLE_NAME ${product_name}
MACOSX_BUNDLE_COPYRIGHT "© 2024 Khronos Group, Inc."
MACOSX_BUNDLE_GUI_IDENTIFIER ${bundle_identifier}
MACOSX_BUNDLE_INFO_PLIST ${INFO_PLIST_IN}
MACOSX_BUNDLE_INFO_STRING "View KTX textures; display via Vulkan."
MACOSX_BUNDLE_ICON_FILE ${KTX_APP_ICON}
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION}
# Because libassimp is built with bitcode disabled. It's not important
# unless submitting to the App Store and currently bitcode is optional.
XCODE_ATTRIBUTE_ENABLE_BITCODE NO
XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH YES
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME ${KTX_APP_ICON_BASENAME}
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2" # iPhone and iPad
# This is to silence a "not stripping because it is signed" warning
# from Xcode during copying by EMBED_FRAMEWORKS. It has no effect
# on the code because (a) all the Vulkan SDK dylibs and frameworks
# are Release config so have no symbols and (b) we need to keep the
# symbols in the Debug config of libktx.
XCODE_ATTRIBUTE_COPY_PHASE_STRIP NO
)
unset(product_name)
unset(bundle_identifier)
# The generated project code for building an Apple bundle automatically
# copies the executable and all files with the RESOURCE property to the
# bundle adjusting for the difference in bundle layout between iOS &
# macOS.
if(IOS)
set_target_properties( vkloadtests PROPERTIES
XCODE_EMBED_FRAMEWORKS "${Vulkan_MoltenVK_LIBRARY};${Vulkan_LIBRARIES};${Vulkan_Layer_VALIDATION}"
XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY "YES"
XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY "YES"
# Set RPATH to find frameworks
INSTALL_RPATH @executable_path/Frameworks
)
else()
# Why is XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY not set here?
# Excellent question. The Vulkan, MoltenVk and VkLayer dylibs are
# all signed by LunarG, the ktx dylib by us so no need. On the other
# hand the Vulkan and MoltenVK frameworks in the iOS SDK are not
# signed. hence it is set there.
set_target_properties( vkloadtests PROPERTIES
XCODE_EMBED_FRAMEWORKS "${Vulkan_LIBRARY_REAL_PATH_NAME};${Vulkan_MoltenVK_LIBRARY};${Vulkan_Layer_VALIDATION}"
# Set RPATH to find frameworks and dylibs
INSTALL_RPATH @executable_path/../Frameworks
)
if(BUILD_SHARED_LIBS)
# XCODE_EMBED_FRAMEWORKS does not appear to support generator
# expressions hence this instead of a genex in the above.
set_property( TARGET vkloadtests
APPEND PROPERTY XCODE_EMBED_FRAMEWORKS
ktx
)
add_custom_command( TARGET vkloadtests POST_BUILD
COMMAND ${CMAKE_COMMAND} -E create_symlink $<TARGET_FILE_NAME:ktx> "$<TARGET_BUNDLE_CONTENT_DIR:vkloadtests>/Frameworks/$<TARGET_SONAME_FILE_NAME:ktx>"
COMMENT "Create symlink for KTX library (ld name to real name"
)
endif()
add_custom_command( TARGET vkloadtests POST_BUILD
COMMAND ${CMAKE_COMMAND} -E create_symlink "${Vulkan_LIBRARY_REAL_FILE_NAME}" "$<TARGET_BUNDLE_CONTENT_DIR:vkloadtests>/Frameworks/${Vulkan_LIBRARY_SONAME_FILE_NAME}"
COMMENT "Create symlink for Vulkan library (ld name to real name)"
)
# Re. SDL3 & assimp: no copy required.: vcpkg libs are static or else
# vcpkg arranges copy. Brew libs cannot be bundled.
# Specify destination for cmake --install.
install(TARGETS vkloadtests
BUNDLE
DESTINATION ${CMAKE_INSTALL_PREFIX}/Applications
COMPONENT VkLoadTestApp
)
## Uncomment for Bundle analysis
# install( CODE "
# include(BundleUtilities)
# verify_app($<TARGET_BUNDLE_DIR:vkloadtests>)
# #fixup_bundle($<TARGET_BUNDLE_DIR:vkloadtests> \"\" \"\")"
# )
endif()
else()
# This is for other platforms.
# This copies the resources next to the executable for ease
# of use during debugging and testing.
add_custom_command( TARGET vkloadtests POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory
$<TARGET_FILE_DIR:vkloadtests>/../resources
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${KTX_RESOURCES} ${SHADER_SOURCES}
$<TARGET_FILE_DIR:vkloadtests>/../resources
)
# To keep the resources (test images and models) close to the
# executable and to be compliant with the Filesystem Hierarchy
# Standard https://refspecs.linuxfoundation.org/FHS_3.0/fhs/index.html
# we have chosen to install the apps and data in /opt/<target>.
# Each target has a `bin` directory with the executable and a
# `resources` directory with the resources. We install a symbolic
# link to the executable in ${CMAKE_INSTALL_BINDIR}, usually
# /usr/local/bin, instead of adding /opt/<target>/bin to $PATH.
#
# TODO: Figure out how to handle libktx so installs of tools only,
# tools + loadtests and loadtests only are supported. Only put
# library in /usr/local/lib? Duplicate it in /opt/<provider>/lib
# from where it is shared by gl3loadtests and vkloadtests? Only
# put it in /opt/<provider>/lib with link from
# ${CMAKE_INSTALL_LIBDIR}? NOTE: if we put lib in /opt/<provider>
# then consider putting the executables in /opt/provider/<target>.
# TODO: Before adding this target to the release packages, ensure
# this RPATH will work for alternate install root.
set_target_properties( vkloadtests PROPERTIES
INSTALL_RPATH "\$ORIGIN;${CMAKE_INSTALL_FULL_LIBDIR}"
)
######### IMPORTANT ######
# When installing via `cmake --install` ALSO install the
# library component. There seems no way to make a dependency.
##########################
# set( destroot "${LOAD_TEST_DESTROOT}/$<TARGET_FILE_NAME:vkloadtests>")
# # NOTE: WHEN RUNNING MANUAL INSTALLS INSTALL library COMPONENT TOO.
# install(TARGETS vkloadtests
# RUNTIME
# DESTINATION ${destroot}/bin
# COMPONENT VkLoadTestApp
# RESOURCE
# DESTINATION ${destroot}/resources
# COMPONENT VkLoadTestApp
# )
# if(LINUX)
# # Add a link from the regular bin directory to put command
# # on PATH.
# install(CODE "
# EXECUTE_PROCESS(COMMAND ln -s ${destroot}/bin/$<TARGET_FILE_NAME:vkloadtests> ${CMAKE_INSTALL_FULL_BINDIR}
# )"
# COMPONENT VkLoadTestApp
# )
# install(FILES
# vkloadtests/resources/linux/vkloadtests.desktop
# DESTINATION /usr/share/applications
# COMPONENT VkLoadTestApp
# )
# endif()
endif()
add_dependencies(
vkloadtests
spirv_shaders
)
@@ -0,0 +1,769 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2017-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @class InstancedSampleBase
* @~English
*
* @brief Base for tests that need instanced drawing of textured quads.
*
* @author Mark Callow, github.com/MarkCallow.
*
* @par Acknowledgement
* Thanks to Sascha Willems' - www.saschawillems.de - for the concept,
* the VulkanTextOverlay class and the shaders used by this test.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <algorithm>
#include <time.h>
#include <vector>
#include "argparser.h"
#include "InstancedSampleBase.h"
#include "ltexceptions.h"
#include "VulkanTextureTranscoder.hpp"
#define VERTEX_BUFFER_BIND_ID 0
#define ENABLE_VALIDATION false
// Vertex layout for this example
struct TAVertex {
float pos[3];
float uv[2];
};
InstancedSampleBase::InstancedSampleBase(VulkanContext& vkctx,
uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath)
: VulkanLoadTestSample(vkctx, width, height, sBasePath)
{
zoom = -15.0f;
rotationSpeed = 0.25f;
rotation = { -15.0f, 35.0f, 0.0f };
tiling = vk::ImageTiling::eOptimal;
uboVS.instance = nullptr;
transcoded = false;
ktxVulkanDeviceInfo vdi;
ktxVulkanDeviceInfo_Construct(&vdi, vkctx.gpu, vkctx.device,
vkctx.queue, vkctx.commandPool, nullptr);
processArgs(szArgs);
KTX_error_code ktxresult;
ktxTexture* kTexture;
std::string ktxfilepath = externalFile ? ktxfilename
: getAssetPath() + ktxfilename;
ktxresult = ktxTexture_CreateFromNamedFile(ktxfilepath.c_str(),
KTX_TEXTURE_CREATE_NO_FLAGS,
&kTexture);
if (KTX_SUCCESS != ktxresult) {
std::stringstream message;
message << "Creation of ktxTexture from \"" << ktxfilepath
<< "\" failed: " << ktxErrorString(ktxresult);
throw std::runtime_error(message.str());
}
if (ktxTexture_NeedsTranscoding(kTexture)) {
TextureTranscoder tc(vkctx);
tc.transcode((ktxTexture2*)kTexture);
transcoded = true;
}
vk::Format vkFormat
= static_cast<vk::Format>(ktxTexture_GetVkFormat(kTexture));
transcodedFormat = vkFormat;
vk::ImageType imageType;
vk::ImageFormatProperties imageFormatProperties;
vk::ImageCreateFlags createFlags;
vk::ImageUsageFlags usageFlags = vk::ImageUsageFlagBits::eSampled;
uint32_t numLevels;
switch (kTexture->numDimensions) {
case 1:
imageType = vk::ImageType::e1D;
break;
case 2:
default: // To keep compilers happy.
imageType = vk::ImageType::e2D;
break;
case 3:
if (kTexture->isArray) {
std::stringstream message;
message << "Texture in \"" << getAssetPath() << szArgs
<< "\" is a 3D array texture which are not supported by Vulkan.";
ktxTexture_Destroy(kTexture);
throw std::runtime_error(message.str());
}
imageType = vk::ImageType::e3D;
break;
}
if (tiling == vk::ImageTiling::eOptimal) {
// Ensure we can copy from staging buffer to image.
usageFlags |= vk::ImageUsageFlagBits::eTransferDst;
}
if (kTexture->generateMipmaps) {
// Ensure we can blit between levels.
usageFlags |= (vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eTransferSrc);
}
vk::Result res
= vkctx.gpu.getImageFormatProperties(vkFormat, imageType, tiling,
usageFlags, createFlags,
&imageFormatProperties);
if (res != vk::Result::eSuccess) {
if (res == vk::Result::eErrorFormatNotSupported) {
throw unsupported_ttype();
} else {
throw bad_vulkan_alloc((int)res, "device.getImageFormatProperties");
}
}
numLevels = kTexture->numLevels;
if (kTexture->generateMipmaps) {
uint32_t max_dim = std::max(std::max(kTexture->baseWidth, kTexture->baseHeight), kTexture->baseDepth);
numLevels = (uint32_t)floor(log2(max_dim)) + 1;
}
if (numLevels > imageFormatProperties.maxMipLevels) {
ktxTexture_Destroy(kTexture);
throw unsupported_ttype();
}
if (kTexture->isArray
&& kTexture->numLevels > imageFormatProperties.maxArrayLayers)
{
ktxTexture_Destroy(kTexture);
throw unsupported_ttype();
}
vk::FormatProperties properties;
vkctx.gpu.getFormatProperties(vkFormat, &properties);
vk::FormatFeatureFlags features = tiling == vk::ImageTiling::eLinear ?
properties.linearTilingFeatures :
properties.optimalTilingFeatures;
vk::FormatFeatureFlags neededFeatures =
vk::FormatFeatureFlagBits::eSampledImage;
if (kTexture->numLevels > 1) {
neededFeatures |=
vk::FormatFeatureFlagBits::eSampledImageFilterLinear;
}
if (kTexture->generateMipmaps) {
neededFeatures |= vk::FormatFeatureFlagBits::eBlitDst
| vk::FormatFeatureFlagBits::eBlitSrc
| vk::FormatFeatureFlagBits::eSampledImageFilterLinear;
}
if ((features & neededFeatures) != neededFeatures) {
ktxTexture_Destroy(kTexture);
throw unsupported_ttype();
}
if (features & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)
filter = vk::Filter::eLinear;
else
filter = vk::Filter::eNearest;
ktxresult =
ktxTexture_VkUploadEx(kTexture, &vdi, &texture,
static_cast<VkImageTiling>(tiling),
VK_IMAGE_USAGE_SAMPLED_BIT,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
if (KTX_SUCCESS != ktxresult) {
std::stringstream message;
message << "ktxTexture_VkUpload failed: " << ktxErrorString(ktxresult);
throw std::runtime_error(message.str());
}
// Checking if KVData contains keys of interest would go here.
ktxTexture_Destroy(kTexture);
ktxVulkanDeviceInfo_Destruct(&vdi);
}
InstancedSampleBase::~InstancedSampleBase()
{
cleanup();
}
void
InstancedSampleBase::resize(uint32_t width, uint32_t height)
{
this->w_width = width;
this->w_height = height;
vkctx.destroyDrawCommandBuffers();
vkctx.createDrawCommandBuffers();
buildCommandBuffers();
updateUniformBufferMatrices();
}
void
InstancedSampleBase::run(uint32_t /*msTicks*/)
{
// Nothing to do since the scene is not animated.
// VulkanLoadTests base class redraws from the command buffer we built.
}
//===================================================================
void
InstancedSampleBase::processArgs(std::string sArgs)
{
// Options descriptor
struct argparser::option longopts[] = {
{"external", argparser::option::no_argument, &externalFile, 1},
{"linear-tiling", argparser::option::no_argument, (int*)&tiling, (int)vk::ImageTiling::eLinear},
{NULL, argparser::option::no_argument, NULL, 0}
};
argvector argv(sArgs);
argparser ap(argv);
int ch;
while ((ch = ap.getopt(nullptr, longopts, nullptr)) != -1) {
switch (ch) {
case 0: break;
default: assert(false); // Error in args in sample table.
}
}
assert(ap.optind < argv.size());
ktxfilename = argv[ap.optind];
}
/* ------------------------------------------------------------------------- */
void
InstancedSampleBase::cleanup()
{
// Clean up used Vulkan resources
// Clean up texture resources
if (sampler)
vkctx.device.destroySampler(sampler);
if (imageView)
vkctx.device.destroyImageView(imageView);
ktxVulkanTexture_Destruct(&texture, vkctx.device, nullptr);
if (pipelines.solid)
vkctx.device.destroyPipeline(pipelines.solid);
if (pipelineLayout)
vkctx.device.destroyPipelineLayout(pipelineLayout);
if (descriptorSetLayout)
vkctx.device.destroyDescriptorSetLayout(descriptorSetLayout);
vkctx.destroyDrawCommandBuffers();
quad.freeResources(vkctx.device);
uniformDataVS.freeResources(vkctx.device);
delete[] uboVS.instance;
}
void
InstancedSampleBase::buildCommandBuffers()
{
vk::CommandBufferBeginInfo cmdBufInfo({}, nullptr);
vk::ClearValue clearValues[2];
clearValues[0].color = defaultClearColor;
clearValues[1].depthStencil = vk::ClearDepthStencilValue(1.0f, 0);
vk::RenderPassBeginInfo renderPassBeginInfo(vkctx.renderPass,
nullptr,
{{0, 0}, {w_width, w_height}},
2,
clearValues);
for (uint32_t i = 0; i < vkctx.drawCmdBuffers.size(); ++i)
{
// Set target frame buffer
renderPassBeginInfo.framebuffer = vkctx.framebuffers[i];
VK_CHECK_RESULT(vkBeginCommandBuffer(vkctx.drawCmdBuffers[i],
&static_cast<const VkCommandBufferBeginInfo&>(cmdBufInfo)));
vkCmdBeginRenderPass(vkctx.drawCmdBuffers[i],
&static_cast<const VkRenderPassBeginInfo&>(renderPassBeginInfo),
VK_SUBPASS_CONTENTS_INLINE);
vk::Viewport viewport(0, 0,
(float)w_width, (float)w_height,
0.0f, 1.0f);
vkCmdSetViewport(vkctx.drawCmdBuffers[i], 0, 1,
&static_cast<const VkViewport&>(viewport));
vk::Rect2D scissor({0, 0}, {w_width, w_height});
vkCmdSetScissor(vkctx.drawCmdBuffers[i], 0, 1,
&static_cast<const VkRect2D&>(scissor));
setSubclassPushConstants(i);
vkCmdBindDescriptorSets(vkctx.drawCmdBuffers[i],
VK_PIPELINE_BIND_POINT_GRAPHICS,
pipelineLayout, 0, 1,
&static_cast<const VkDescriptorSet&>(descriptorSet),
0, NULL);
VkDeviceSize offsets[1] = { 0 };
vkCmdBindVertexBuffers(vkctx.drawCmdBuffers[i],
VERTEX_BUFFER_BIND_ID, 1,
&static_cast<const VkBuffer&>(quad.vertices.buf),
offsets);
vkCmdBindIndexBuffer(vkctx.drawCmdBuffers[i], quad.indices.buf,
0, VK_INDEX_TYPE_UINT32);
vkCmdBindPipeline(vkctx.drawCmdBuffers[i],
VK_PIPELINE_BIND_POINT_GRAPHICS,
pipelines.solid);
vkCmdDrawIndexed(vkctx.drawCmdBuffers[i], quad.indexCount,
instanceCount, 0, 0, 0);
vkCmdEndRenderPass(vkctx.drawCmdBuffers[i]);
VK_CHECK_RESULT(vkEndCommandBuffer(vkctx.drawCmdBuffers[i]));
}
}
// Setup vertices for a single uv-mapped quad
void
InstancedSampleBase::generateQuad()
{
#define dim 2.5f
std::vector<TAVertex> vertexBuffer =
{
{ { dim, dim, 0.0f }, { 1.0f, 1.0f } },
{ { -dim, dim, 0.0f }, { 0.0f, 1.0f } },
{ { -dim, -dim, 0.0f }, { 0.0f, 0.0f } },
{ { dim, -dim, 0.0f }, { 1.0f, 0.0f } }
};
#undef dim
vkctx.createBuffer(
vk::BufferUsageFlagBits::eVertexBuffer,
vertexBuffer.size() * sizeof(TAVertex),
vertexBuffer.data(),
&quad.vertices.buf,
&quad.vertices.mem);
// Setup indices
std::vector<uint32_t> indexBuffer = { 0,1,2, 2,3,0 };
quad.indexCount = static_cast<uint32_t>(indexBuffer.size());
vkctx.createBuffer(
vk::BufferUsageFlagBits::eIndexBuffer,
indexBuffer.size() * sizeof(uint32_t),
indexBuffer.data(),
&quad.indices.buf,
&quad.indices.mem);
}
void
InstancedSampleBase::setupVertexDescriptions()
{
// Binding description
vertices.bindingDescriptions.resize(1);
vertices.bindingDescriptions[0] =
vk::VertexInputBindingDescription(
VERTEX_BUFFER_BIND_ID,
sizeof(TAVertex),
vk::VertexInputRate::eVertex);
// Attribute descriptions
// Describes memory layout and shader positions
vertices.attributeDescriptions.resize(2);
// Location 0 : Position
vertices.attributeDescriptions[0] =
vk::VertexInputAttributeDescription(
0,
VERTEX_BUFFER_BIND_ID,
vk::Format::eR32G32B32Sfloat,
0);
// Location 1 : Texture coordinates
vertices.attributeDescriptions[1] =
vk::VertexInputAttributeDescription(
1,
VERTEX_BUFFER_BIND_ID,
vk::Format::eR32G32Sfloat,
sizeof(float) * 3);
vertices.inputState = vk::PipelineVertexInputStateCreateInfo();
vertices.inputState.vertexBindingDescriptionCount =
static_cast<uint32_t>(vertices.bindingDescriptions.size());
vertices.inputState.pVertexBindingDescriptions = vertices.bindingDescriptions.data();
vertices.inputState.vertexAttributeDescriptionCount =
static_cast<uint32_t>(vertices.attributeDescriptions.size());
vertices.inputState.pVertexAttributeDescriptions = vertices.attributeDescriptions.data();
}
void
InstancedSampleBase::setupDescriptorPool()
{
// Example uses one ubo and one image sampler
std::vector<vk::DescriptorPoolSize> poolSizes =
{
{vk::DescriptorType::eUniformBuffer, 1},
{vk::DescriptorType::eCombinedImageSampler, 1}
};
vk::DescriptorPoolCreateInfo descriptorPoolInfo(
{},
2,
static_cast<uint32_t>(poolSizes.size()),
poolSizes.data());
vk::Result res = vkctx.device.createDescriptorPool(&descriptorPoolInfo,
nullptr,
&descriptorPool);
if (res != vk::Result::eSuccess) {
throw bad_vulkan_alloc((int)res, "createDescriptorPool");
}
}
void
InstancedSampleBase::setupDescriptorSetLayout()
{
DescriptorBindings descriptorBindings =
{
// Binding 0 : Vertex shader uniform buffer
{0,
vk::DescriptorType::eUniformBuffer,
1,
vk::ShaderStageFlagBits::eVertex},
// Binding 1 : Fragment shader image sampler
{1,
vk::DescriptorType::eCombinedImageSampler,
1,
vk::ShaderStageFlagBits::eFragment},
};
//addSubclassDescriptors(descriptorBindings);
vk::DescriptorSetLayoutCreateInfo descriptorLayout(
{},
static_cast<uint32_t>(descriptorBindings.size()),
descriptorBindings.data());
vk::Result res
= vkctx.device.createDescriptorSetLayout(&descriptorLayout,
nullptr,
&descriptorSetLayout);
if (res != vk::Result::eSuccess) {
throw bad_vulkan_alloc((int)res, "createDescriptorSetLayout");
}
vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo(
{},
1,
&descriptorSetLayout);
std::vector<vk::PushConstantRange> pushConstantRanges;
addSubclassPushConstantRanges(pushConstantRanges);
if (pushConstantRanges.size() > 0) {
pipelineLayoutCreateInfo.setPushConstantRanges(pushConstantRanges);
}
res = vkctx.device.createPipelineLayout(&pipelineLayoutCreateInfo,
nullptr,
&pipelineLayout);
if (res != vk::Result::eSuccess) {
throw bad_vulkan_alloc((int)res, "createPipelineLayout");
}
}
void
InstancedSampleBase::setupDescriptorSet()
{
vk::DescriptorSetAllocateInfo allocInfo(
descriptorPool,
1,
&descriptorSetLayout);
vk::Result res
= vkctx.device.allocateDescriptorSets(&allocInfo, &descriptorSet);
if (res != vk::Result::eSuccess) {
throw bad_vulkan_alloc((int)res, "allocateDescriptorSets");
}
// Image descriptor for the color map texture
vk::DescriptorImageInfo texDescriptor(
sampler,
imageView,
vk::ImageLayout::eShaderReadOnlyOptimal);
std::vector<vk::WriteDescriptorSet> writeDescriptorSets;
// Binding 0 : Vertex shader uniform buffer
writeDescriptorSets.push_back(vk::WriteDescriptorSet(
descriptorSet,
0,
0,
1,
vk::DescriptorType::eUniformBuffer,
nullptr,
&uniformDataVS.descriptor)
);
// Binding 1 : Fragment shader texture sampler
writeDescriptorSets.push_back(vk::WriteDescriptorSet(
descriptorSet,
1,
0,
1,
vk::DescriptorType::eCombinedImageSampler,
&texDescriptor)
);
vkctx.device.updateDescriptorSets(
static_cast<uint32_t>(writeDescriptorSets.size()),
writeDescriptorSets.data(),
0,
nullptr);
}
void
InstancedSampleBase::preparePipelines(const char* const fragShaderName,
const char* const vertShaderName,
uint32_t instanceCountConstId)
{
vk::PipelineInputAssemblyStateCreateInfo inputAssemblyState(
{},
vk::PrimitiveTopology::eTriangleList);
vk::PipelineRasterizationStateCreateInfo rasterizationState;
// Must be false because we haven't enabled the depthClamp device feature.
rasterizationState.depthClampEnable = false;
rasterizationState.rasterizerDiscardEnable = false;
rasterizationState.polygonMode = vk::PolygonMode::eFill;
rasterizationState.cullMode = vk::CullModeFlagBits::eNone;
rasterizationState.frontFace = vk::FrontFace::eCounterClockwise;
rasterizationState.lineWidth = 1.0f;
vk::PipelineColorBlendAttachmentState blendAttachmentState;
blendAttachmentState.blendEnable = false;
//blendAttachmentState.colorWriteMask = 0xf;
blendAttachmentState.colorWriteMask = vk::ColorComponentFlagBits::eR
| vk::ColorComponentFlagBits::eG
| vk::ColorComponentFlagBits::eB
| vk::ColorComponentFlagBits::eA;
vk::PipelineColorBlendStateCreateInfo colorBlendState;
colorBlendState.attachmentCount = 1;
colorBlendState.pAttachments = &blendAttachmentState;
vk::PipelineDepthStencilStateCreateInfo depthStencilState;
depthStencilState.depthTestEnable = true;
depthStencilState.depthWriteEnable = true;
depthStencilState.depthCompareOp = vk::CompareOp::eLessOrEqual;
vk::PipelineViewportStateCreateInfo viewportState;
viewportState.viewportCount = 1;
viewportState.scissorCount = 1;
vk::PipelineMultisampleStateCreateInfo multisampleState;
multisampleState.rasterizationSamples = vk::SampleCountFlagBits::e1;
std::vector<vk::DynamicState> dynamicStateEnables = {
vk::DynamicState::eViewport,
vk::DynamicState::eScissor
};
vk::PipelineDynamicStateCreateInfo dynamicState(
{},
static_cast<uint32_t>(dynamicStateEnables.size()),
dynamicStateEnables.data());
// Load shaders
std::array<vk::PipelineShaderStageCreateInfo,2> shaderStages;
std::string filepath = getAssetPath();
// What a lot of code to set a single constant value.
vk::SpecializationInfo specializationInfo;
vk::SpecializationMapEntry mapEntries[1];
mapEntries[0].setConstantID(instanceCountConstId);
mapEntries[0].setOffset(0);
mapEntries[0].setSize(4);
specializationInfo.setMapEntryCount(1);
specializationInfo.setPMapEntries(mapEntries);
specializationInfo.setPData(&instanceCount);
specializationInfo.setDataSize(sizeof(instanceCount));
shaderStages[0].pSpecializationInfo = &specializationInfo;
shaderStages[0] = loadShader(filepath + vertShaderName,
vk::ShaderStageFlagBits::eVertex);
shaderStages[1] = loadShader(filepath + fragShaderName,
vk::ShaderStageFlagBits::eFragment);
vk::GraphicsPipelineCreateInfo pipelineCreateInfo;
pipelineCreateInfo.layout = pipelineLayout;
pipelineCreateInfo.renderPass = vkctx.renderPass;
pipelineCreateInfo.pVertexInputState = &vertices.inputState;
pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
pipelineCreateInfo.pRasterizationState = &rasterizationState;
pipelineCreateInfo.pColorBlendState = &colorBlendState;
pipelineCreateInfo.pMultisampleState = &multisampleState;
pipelineCreateInfo.pViewportState = &viewportState;
pipelineCreateInfo.pDepthStencilState = &depthStencilState;
pipelineCreateInfo.pDynamicState = &dynamicState;
pipelineCreateInfo.stageCount = (uint32_t)shaderStages.size();
pipelineCreateInfo.pStages = shaderStages.data();
vk::Result res
= vkctx.device.createGraphicsPipelines(vkctx.pipelineCache, 1,
&pipelineCreateInfo, nullptr,
&pipelines.solid);
if (res != vk::Result::eSuccess) {
throw bad_vulkan_alloc((int)res, "createGraphicsPipelines");
}
}
#define _PAD16(nbytes) (ktx_uint32_t)(16 * ceilf((float)(nbytes) / 16))
void
InstancedSampleBase::prepareUniformBuffers(uint32_t shaderDeclaredInstances)
{
uboVS.instance = new UboInstanceData[instanceCount];
// Elements of the array of UboInstanceData will be aligned on 16-byte
// boundaries per the std140 rule for mat4/vec4. _PAD16 is unnecessary
// right now but will become so if anything is added to the ubo before
// the UboInstanceData. _PAD16 is put here as a warning.
uint32_t uboSize = _PAD16(sizeof(uboVS.matrices))
//+ instanceCount * sizeof(UboInstanceData);
+ shaderDeclaredInstances * sizeof(UboInstanceData);
// Vertex shader uniform buffer block
vkctx.createBuffer(
vk::BufferUsageFlagBits::eUniformBuffer,
vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent,
uboSize,
nullptr,
&uniformDataVS.buffer,
&uniformDataVS.memory,
&uniformDataVS.descriptor);
// MoltenVK can't specialize array-length constants, an MSL limitation,
// so we have to potentially modify instanceCount. We can't just
// declare a very long array in the shaders because we get a MoltenVK
// validation error when the allocation we make above is less than
// the declared length. Making the array length 1, works on macOS
// but not on iOS where only 1 instance is drawn correctly. See
// MoltenVK issues 1420 and 1421.
// https://github.com/KhronosGroup/MoltenVK/issues/1421.
// Paren around std::min avoids a SNAFU that windef.h has a "min" macro.
instanceCount = (std::min)(shaderDeclaredInstances, instanceCount);
// Array indices and model matrices are fixed
float offset = -1.5f;
float center = (instanceCount * offset) / 2;
for (uint32_t i = 0; i < instanceCount; i++)
{
// Instance model matrix
uboVS.instance[i].model = glm::translate(glm::mat4(), glm::vec3(0.0f, i * offset - center, 0.0f));
uboVS.instance[i].model = glm::rotate(uboVS.instance[i].model, glm::radians(60.0f), glm::vec3(1.0f, 0.0f, 0.0f));
}
// Update instanced part of the uniform buffer
uint8_t *pData;
// N.B. See comment re _PAD16 before uboSize above.
uint32_t dataOffset = _PAD16(sizeof(uboVS.matrices));
uint32_t dataSize = instanceCount * sizeof(UboInstanceData);
VK_CHECK_RESULT(vkMapMemory(vkctx.device, uniformDataVS.memory, dataOffset, dataSize, 0, (void **)&pData));
memcpy(pData, uboVS.instance, dataSize);
vkUnmapMemory(vkctx.device, uniformDataVS.memory);
updateUniformBufferMatrices();
}
void
InstancedSampleBase::updateUniformBufferMatrices()
{
// Only updates the uniform buffer block part containing the global matrices
// Projection
uboVS.matrices.projection = glm::perspective(glm::radians(60.0f), (float)w_width / (float)w_height, 0.001f, 256.0f);
// View
uboVS.matrices.view = glm::translate(glm::mat4(), glm::vec3(0.0f, -1.0f, zoom));
uboVS.matrices.view *= glm::translate(glm::mat4(), cameraPos);
uboVS.matrices.view = glm::rotate(uboVS.matrices.view, glm::radians(rotation.x), glm::vec3(1.0f, 0.0f, 0.0f));
uboVS.matrices.view = glm::rotate(uboVS.matrices.view, glm::radians(rotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
uboVS.matrices.view = glm::rotate(uboVS.matrices.view, glm::radians(rotation.z), glm::vec3(0.0f, 0.0f, 1.0f));
// Only update the matrices part of the uniform buffer
uint8_t *pData;
VK_CHECK_RESULT(vkMapMemory(vkctx.device, uniformDataVS.memory, 0, sizeof(uboVS.matrices), 0, (void **)&pData));
memcpy(pData, &uboVS.matrices, sizeof(uboVS.matrices));
vkUnmapMemory(vkctx.device, uniformDataVS.memory);
}
void
InstancedSampleBase::prepareSamplerAndView()
{
// Create sampler.
vk::SamplerCreateInfo samplerInfo;
// Set the non-default values
samplerInfo.magFilter = filter;
samplerInfo.minFilter = filter;
samplerInfo.mipmapMode = vk::SamplerMipmapMode::eLinear;
samplerInfo.maxLod = (float)texture.levelCount;
if (vkctx.gpuFeatures.samplerAnisotropy == VK_TRUE) {
samplerInfo.anisotropyEnable = VK_TRUE;
samplerInfo.maxAnisotropy = 8;
} else {
// vulkan.hpp needs fixing
samplerInfo.maxAnisotropy = 1.0;
}
samplerInfo.borderColor = vk::BorderColor::eFloatOpaqueWhite;
// To make viewer more useful in verifying the content of 3d textures.
samplerInfo.addressModeW = vk::SamplerAddressMode::eClampToEdge;
sampler = vkctx.device.createSampler(samplerInfo);
// Create image view.
// Textures are not directly accessed by the shaders and are abstracted
// by image views containing additional information and sub resource
// ranges.
vk::ImageViewCreateInfo viewInfo;
// Set the non-default values.
viewInfo.image = texture.image;
viewInfo.format = static_cast<vk::Format>(texture.imageFormat);
viewInfo.viewType
= static_cast<vk::ImageViewType>(texture.viewType);
viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
viewInfo.subresourceRange.layerCount = texture.layerCount;
viewInfo.subresourceRange.levelCount = texture.levelCount;
imageView = vkctx.device.createImageView(viewInfo);
}
void
InstancedSampleBase::prepare(const char* const fragShaderName,
const char* const vertShaderName,
uint32_t instanceCountConstId,
uint32_t instanceCountIn,
uint32_t shaderDeclaredInstances)
{
this->instanceCount = instanceCountIn;
prepareSamplerAndView();
setupVertexDescriptions();
generateQuad();
prepareUniformBuffers(shaderDeclaredInstances);
setupDescriptorSetLayout();
preparePipelines(fragShaderName, vertShaderName,
instanceCountConstId);
setupDescriptorPool();
setupDescriptorSet();
vkctx.createDrawCommandBuffers();
buildCommandBuffers();
}
const char*
InstancedSampleBase::customizeTitle(const char* const baseTitle)
{
if (transcoded) {
this->title = baseTitle;
this->title += " Transcoded to ";
this->title += vkFormatString((VkFormat)transcodedFormat);
return this->title.c_str();
}
return baseTitle;
}
@@ -0,0 +1,128 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2017-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _INSTANCE_SAMPLE_BASE_H_
#define _INSTANCE_SAMPLE_BASE_H_
#include <vector>
#include "VulkanLoadTestSample.h"
#include <ktxvulkan.h>
#include <glm/gtc/matrix_transform.hpp>
class InstancedSampleBase : public VulkanLoadTestSample
{
public:
InstancedSampleBase(VulkanContext& vkctx,
uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath);
virtual ~InstancedSampleBase();
virtual void resize(uint32_t width, uint32_t height);
virtual void run(uint32_t msTicks);
//virtual void getOverlayText(VulkanTextOverlay *textOverlay, float yOffset);
virtual const char* customizeTitle(const char* const title);
protected:
ktxVulkanTexture texture;
vk::Sampler sampler;
vk::ImageView imageView;
vk::ImageTiling tiling;
vk::Filter filter;
uint32_t instanceCount;
bool transcoded;
vk::Format transcodedFormat;
std::string title;
struct {
vk::PipelineVertexInputStateCreateInfo inputState;
std::vector<vk::VertexInputBindingDescription> bindingDescriptions;
std::vector<vk::VertexInputAttributeDescription> attributeDescriptions;
} vertices;
MeshBuffer quad;
UniformData uniformDataVS;
struct UboInstanceData {
// Model matrix
glm::mat4 model;
};
struct {
// Global matrices
struct {
glm::mat4 projection;
glm::mat4 view;
} matrices;
// N.B. The UBO structure declared in the shader has the array of
// instance data inside the structure rather than pointed at from the
// structure. The start of the array will be aligned on a 16-byte
// boundary as it starts with a matrix.
//
// Separate data for each instance
UboInstanceData *instance;
} uboVS;
struct {
vk::Pipeline solid;
} pipelines;
vk::PipelineLayout pipelineLayout;
vk::DescriptorSet descriptorSet;
vk::DescriptorSetLayout descriptorSetLayout;
vk::DescriptorPool descriptorPool;
void cleanup();
void buildCommandBuffers();
// Setup vertices for a single uv-mapped quad
void generateQuad();
using DescriptorBindings = std::vector<vk::DescriptorSetLayoutBinding>;
using PushConstantRanges = std::vector<vk::PushConstantRange>;
virtual void addSubclassDescriptors(DescriptorBindings&) { }
virtual void addSubclassPushConstantRanges(PushConstantRanges&) { }
virtual void setSubclassPushConstants(uint32_t) { }
void setupVertexDescriptions();
void setupDescriptorPool();
void setupDescriptorSetLayout();
void setupDescriptorSet();
void preparePipelines(const char* const fragShaderName,
const char* const vertShaderName,
uint32_t instanceCountConstId);
void prepareUniformBuffers(// See note in prepare declaration.
uint32_t shaderDeclaredInstances);
void updateUniformBufferMatrices();
void prepareSamplerAndView();
void prepare(const char* const fragShaderName,
const char* const vertShaderName,
uint32_t instanceCountConstId,
uint32_t instanceCount,
// Solely because of MoltenVK issue #1420.
// It can't specialize array length constants.
uint32_t shaderDeclaredInstances);
void processArgs(std::string sArgs);
virtual void viewChanged()
{
updateUniformBufferMatrices();
}
};
#endif /* _INSTANCE_SAMPLE_BASE_H_ */
+818
View File
@@ -0,0 +1,818 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab : */
/*
* Copyright 2017-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @class Texture
* @~English
*
* @brief Test loading of 2D textures.
*
* @author Mark Callow, github.com/MarkCallow.
*
* @par Acknowledgement
* Thanks to Sascha Willems' - www.saschawillems.de - for the concept,
* the VulkanTextOverlay class and the shaders used by this test.
*/
#if defined(_WIN32)
#define _CRT_SECURE_NO_WARNINGS // For sscanf
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <exception>
#include <vector>
#include "argparser.h"
#include "Texture.h"
#include "VulkanTextureTranscoder.hpp"
#include "ltexceptions.h"
#define VERTEX_BUFFER_BIND_ID 0
#define ENABLE_VALIDATION false
// Vertex layout for this example
struct Vertex {
std::array<float, 3> pos;
std::array<float, 2> uv;
std::array<float, 3> normal;
std::array<float, 3> color;
};
VulkanLoadTestSample*
Texture::create(VulkanContext& vkctx,
uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath)
{
return new Texture(vkctx, width, height, szArgs, sBasePath);
}
Texture::Texture(VulkanContext& vkctx,
uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath)
: VulkanLoadTestSample(vkctx, width, height, sBasePath)
{
zoom = -2.5f;
rotation = { 0.0f, 15.0f, 0.0f };
tiling = vk::ImageTiling::eOptimal;
useSubAlloc = UseSuballocator::No;
rgbcolor upperLeftColor{ 0.7f, 0.1f, 0.2f };
rgbcolor lowerLeftColor{ 0.8f, 0.9f, 0.3f };
rgbcolor upperRightColor{ 0.4f, 1.0f, 0.5f };
rgbcolor lowerRightColor{ 0.0f, 0.6f, 0.1f };
transcoded = false;
quadColor = { upperLeftColor, lowerLeftColor,
upperRightColor, lowerRightColor };
ktxVulkanDeviceInfo vdi;
ktxVulkanDeviceInfo_Construct(&vdi, vkctx.gpu, vkctx.device,
vkctx.queue, vkctx.commandPool, nullptr);
processArgs(szArgs);
KTX_error_code ktxresult;
ktxTexture* kTexture;
std::string ktxfilepath = externalFile ? ktxfilename
: getAssetPath() + ktxfilename;
ktxresult = ktxTexture_CreateFromNamedFile(ktxfilepath.c_str(),
KTX_TEXTURE_CREATE_NO_FLAGS,
&kTexture);
if (KTX_SUCCESS != ktxresult) {
std::stringstream message;
message << "Creation of ktxTexture from \"" << ktxfilepath
<< "\" failed: " << ktxErrorString(ktxresult);
throw std::runtime_error(message.str());
}
if (ktxTexture_NeedsTranscoding(kTexture)) {
TextureTranscoder tc(vkctx);
tc.transcode((ktxTexture2*)kTexture);
transcoded = true;
}
vk::Format vkFormat
= static_cast<vk::Format>(ktxTexture_GetVkFormat(kTexture));
transcodedFormat = vkFormat;
vk::FormatProperties properties;
vkctx.gpu.getFormatProperties(vkFormat, &properties);
vk::FormatFeatureFlags& features = tiling == vk::ImageTiling::eLinear ?
properties.linearTilingFeatures :
properties.optimalTilingFeatures;
vk::FormatFeatureFlags wantedFeatures =
vk::FormatFeatureFlagBits::eSampledImage
| vk::FormatFeatureFlagBits::eSampledImageFilterLinear;
if (!(features & wantedFeatures)) {
ktxTexture_Destroy(kTexture);
throw unsupported_ttype();
}
if (useSubAlloc == UseSuballocator::Yes)
{
VkInstance vkInst = vkctx.instance;
VMA_CALLBACKS::InitVMA(vdi.physicalDevice, vdi.device, vkInst, vdi.deviceMemoryProperties);
ktxresult = ktxTexture_VkUploadEx_WithSuballocator(kTexture, &vdi, &texture,
static_cast<VkImageTiling>(tiling),
VK_IMAGE_USAGE_SAMPLED_BIT,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, &subAllocatorCallbacks);
}
else // Keep separate call so ktxTexture_VkUploadEx is also tested.
ktxresult = ktxTexture_VkUploadEx(kTexture, &vdi, &texture,
static_cast<VkImageTiling>(tiling),
VK_IMAGE_USAGE_SAMPLED_BIT,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
if (KTX_SUCCESS != ktxresult) {
std::stringstream message;
message << "ktxTexture_VkUpload failed: " << ktxErrorString(ktxresult);
throw std::runtime_error(message.str());
}
if (kTexture->orientation.x == KTX_ORIENT_X_LEFT)
sign_s = -1;
if (kTexture->orientation.y == KTX_ORIENT_Y_UP)
sign_t = -1;
ktx_uint32_t swizzleLen;
char* swizzleStr;
ktxresult = ktxHashList_FindValue(&kTexture->kvDataHead, KTX_SWIZZLE_KEY,
&swizzleLen, (void**)&swizzleStr);
if (ktxresult == KTX_SUCCESS && swizzleLen == 5) {
if (gpuSupportsSwizzle()) {
swizzle.r = swizzleStr[0] == 'r' ? vk::ComponentSwizzle::eR
: swizzleStr[0] == 'g' ? vk::ComponentSwizzle::eG
: swizzleStr[0] == 'b' ? vk::ComponentSwizzle::eB
: swizzleStr[0] == 'a' ? vk::ComponentSwizzle::eA
: swizzleStr[0] == '0' ? vk::ComponentSwizzle::eZero
: vk::ComponentSwizzle::eOne;
swizzle.g = swizzleStr[1] == 'r' ? vk::ComponentSwizzle::eR
: swizzleStr[1] == 'g' ? vk::ComponentSwizzle::eG
: swizzleStr[1] == 'b' ? vk::ComponentSwizzle::eB
: swizzleStr[1] == 'a' ? vk::ComponentSwizzle::eA
: swizzleStr[1] == '0' ? vk::ComponentSwizzle::eZero
: vk::ComponentSwizzle::eOne;
swizzle.b = swizzleStr[2] == 'r' ? vk::ComponentSwizzle::eR
: swizzleStr[2] == 'g' ? vk::ComponentSwizzle::eG
: swizzleStr[2] == 'b' ? vk::ComponentSwizzle::eB
: swizzleStr[2] == 'a' ? vk::ComponentSwizzle::eA
: swizzleStr[2] == '0' ? vk::ComponentSwizzle::eZero
: vk::ComponentSwizzle::eOne;
swizzle.a = swizzleStr[3] == 'r' ? vk::ComponentSwizzle::eR
: swizzleStr[3] == 'g' ? vk::ComponentSwizzle::eG
: swizzleStr[3] == 'b' ? vk::ComponentSwizzle::eB
: swizzleStr[3] == 'a' ? vk::ComponentSwizzle::eA
: swizzleStr[3] == '0' ? vk::ComponentSwizzle::eZero
: vk::ComponentSwizzle::eOne;
} else {
std::stringstream message;
message << "Input file has swizzle metadata but "
<< "app is running on a VK_KHR_portability_subset device "
<< "that does not support swizzling.";
// I have absolutely no idea why the following line makes clang
// raise an error about no matching conversion from
// std::__1::basic_stringstream to unsupported_ttype
// so I've resorted to using a temporary variable.
//throw(unsupported_ttype(message.str());
std::string msg = message.str();
throw(unsupported_ttype(msg));
}
}
ktxTexture_Destroy(kTexture);
ktxVulkanDeviceInfo_Destruct(&vdi);
try {
prepare();
} catch (std::exception& e) {
(void)e; // To quiet unused variable warnings from some compilers.
cleanup();
throw;
}
}
Texture::~Texture()
{
cleanup();
}
void
Texture::resize(uint32_t width, uint32_t height)
{
this->w_width = width;
this->w_height = height;
vkctx.destroyDrawCommandBuffers();
vkctx.createDrawCommandBuffers();
buildCommandBuffers();
updateUniformBuffers();
}
void
Texture::run(uint32_t /*msTicks*/)
{
// Nothing to do since the scene is not animated.
// VulkanLoadTests base class redraws from the command buffer we built.
}
//===================================================================
void
Texture::processArgs(std::string sArgs)
{
// Options descriptor
struct argparser::option longopts[] = {
{"external", argparser::option::no_argument, &externalFile, 1},
{"linear-tiling", argparser::option::no_argument, (int*)&tiling, (int)vk::ImageTiling::eLinear},
{"use-vma", argparser::option::no_argument, (int*)&useSubAlloc, (int)UseSuballocator::Yes},
{"qcolor", argparser::option::required_argument, NULL, 1},
{NULL, argparser::option::no_argument, NULL, 0}
};
argvector argv(sArgs);
argparser ap(argv);
int ch;
while ((ch = ap.getopt(nullptr, longopts, nullptr)) != -1) {
switch (ch) {
case 0: break;
case 1:
{
std::istringstream in(ap.optarg);
rgbcolor clr;
int i;
for (i = 0; i < 4 && !in.eof(); i++) {
in >> clr[0] >> skip(",") >> clr[1] >> skip(",") >> clr[2];
quadColor[i] = clr;
if (!in.eof())
in >> skip(",");
}
assert(!in.fail() && (i == 1 || i == 4));
if (i == 1) {
for(; i < 4; i++)
quadColor[i] = quadColor[0];
}
break;
}
default: assert(false); // Error in args in sample table.
}
}
assert(ap.optind < argv.size());
ktxfilename = argv[ap.optind];
}
/* ------------------------------------------------------------------------- */
// It is difficult to have these members clean up up their own mess, hence
// this. Some of them are vulkan.hpp objects that have no destructors and
// no record of the device. We could add destructors for our own but each
// would have to remember the device.
void
Texture::cleanup()
{
// Clean up used Vulkan resources
vkctx.destroyDrawCommandBuffers();
if (sampler)
vkctx.device.destroySampler(sampler);
if (imageView)
vkctx.device.destroyImageView(imageView);
if (useSubAlloc == UseSuballocator::Yes)
{
VkDevice vkDev = vkctx.device;
(void)ktxVulkanTexture_Destruct_WithSuballocator(&texture, vkDev, VK_NULL_HANDLE, &subAllocatorCallbacks);
VMA_CALLBACKS::DestroyVMA();
}
else // Keep separate call so ktxVulkanTexture_Destruct is also tested.
ktxVulkanTexture_Destruct(&texture, vkctx.device, nullptr);
if (pipelines.solid)
vkctx.device.destroyPipeline(pipelines.solid);
if (pipelineLayout)
vkctx.device.destroyPipelineLayout(pipelineLayout);
if (descriptorSetLayout)
vkctx.device.destroyDescriptorSetLayout(descriptorSetLayout);
quad.freeResources(vkctx.device);
uniformDataVS.freeResources(vkctx.device);
}
void
Texture::buildCommandBuffers()
{
vk::CommandBufferBeginInfo cmdBufInfo({}, nullptr);
vk::ClearValue clearValues[2];
clearValues[0].color = defaultClearColor;
clearValues[1].depthStencil = vk::ClearDepthStencilValue(1.0f, 0);
vk::RenderPassBeginInfo renderPassBeginInfo(vkctx.renderPass,
nullptr,
{{0, 0}, {w_width, w_height}},
2,
clearValues);
for (uint32_t i = 0; i < vkctx.drawCmdBuffers.size(); ++i)
{
// Set target frame buffer
renderPassBeginInfo.framebuffer = vkctx.framebuffers[i];
VK_CHECK_RESULT(vkBeginCommandBuffer(vkctx.drawCmdBuffers[i],
&static_cast<const VkCommandBufferBeginInfo&>(cmdBufInfo)));
vkCmdBeginRenderPass(vkctx.drawCmdBuffers[i],
&static_cast<const VkRenderPassBeginInfo&>(renderPassBeginInfo),
VK_SUBPASS_CONTENTS_INLINE);
vk::Viewport viewport(0, 0,
(float)w_width, (float)w_height,
0.0f, 1.0f);
vkCmdSetViewport(vkctx.drawCmdBuffers[i], 0, 1,
&static_cast<const VkViewport&>(viewport));
vk::Rect2D scissor({0, 0}, {w_width, w_height});
vkCmdSetScissor(vkctx.drawCmdBuffers[i], 0, 1,
&static_cast<const VkRect2D&>(scissor));
vkCmdBindDescriptorSets(vkctx.drawCmdBuffers[i],
VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1,
&static_cast<const VkDescriptorSet&>(descriptorSet), 0, NULL);
vkCmdBindPipeline(vkctx.drawCmdBuffers[i],
VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.solid);
VkDeviceSize offsets[1] = { 0 };
vkCmdBindVertexBuffers(vkctx.drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID,
1, &static_cast<const VkBuffer&>(quad.vertices.buf), offsets);
vkCmdBindIndexBuffer(vkctx.drawCmdBuffers[i], quad.indices.buf, 0,
VK_INDEX_TYPE_UINT32);
vkCmdDrawIndexed(vkctx.drawCmdBuffers[i], quad.indexCount, 1, 0, 0, 0);
vkCmdEndRenderPass(vkctx.drawCmdBuffers[i]);
VK_CHECK_RESULT(vkEndCommandBuffer(vkctx.drawCmdBuffers[i]));
}
}
void
Texture::generateQuad()
{
// Setup vertices for a single uv-mapped quad
#define DIM 1.0f
#define NORMAL { 0.0f, 0.0f, 1.0f }
std::vector<Vertex> vertexBuffer = {
{ { -DIM, -DIM, 0.0f }, { 0.0f, 0.0f }, NORMAL, { quadColor[0] } },
{ { -DIM, DIM, 0.0f }, { 0.0f, 1.0f }, NORMAL, { quadColor[1] } },
{ { DIM, -DIM, 0.0f }, { 1.0f, 0.0f }, NORMAL, { quadColor[2] } },
{ { DIM, DIM, 0.0f }, { 1.0f, 1.0f }, NORMAL, { quadColor[3] } }
};
#undef DIM
#undef NORMAL
if (sign_s < 0 || sign_t < 0) {
// Transform the texture coordinates to get correct image orientation.
for (uint32_t i = 0; i < vertexBuffer.size(); i++) {
if (sign_t < 1) {
vertexBuffer[i].uv[1] = vertexBuffer[i].uv[1] * -1 + 1;
}
if (sign_s < 1) {
vertexBuffer[i].uv[0] = vertexBuffer[i].uv[0] * -1 + 1;
}
}
}
vkctx.createBuffer(
vk::BufferUsageFlagBits::eVertexBuffer,
vertexBuffer.size() * sizeof(Vertex),
vertexBuffer.data(),
&quad.vertices.buf,
&quad.vertices.mem);
// Setup indices
std::vector<uint32_t> indexBuffer = { 0,1,2,3 };
quad.indexCount = static_cast<uint32_t>(indexBuffer.size());
vkctx.createBuffer(
vk::BufferUsageFlagBits::eIndexBuffer,
indexBuffer.size() * sizeof(uint32_t),
indexBuffer.data(),
&quad.indices.buf,
&quad.indices.mem);
}
void
Texture::setupVertexDescriptions()
{
// Binding description
vertices.bindingDescriptions.resize(1);
vertices.bindingDescriptions[0] =
vk::VertexInputBindingDescription(
VERTEX_BUFFER_BIND_ID,
sizeof(Vertex),
vk::VertexInputRate::eVertex);
//#define OFFSET(f) (&(((struct Vertex*)0)->f) - &(struct Vertex*)0)
// Attribute descriptions
// Describes memory layout and shader positions
vertices.attributeDescriptions.resize(4);
// Location 0 : Position
vertices.attributeDescriptions[0] =
vk::VertexInputAttributeDescription(
0,
VERTEX_BUFFER_BIND_ID,
vk::Format::eR32G32B32Sfloat,
0);
// Location 1 : Texture coordinates
vertices.attributeDescriptions[1] =
vk::VertexInputAttributeDescription(
1,
VERTEX_BUFFER_BIND_ID,
vk::Format::eR32G32Sfloat,
sizeof(float) * 3);
// Location 2 : Vertex normal
vertices.attributeDescriptions[2] =
vk::VertexInputAttributeDescription(
2,
VERTEX_BUFFER_BIND_ID,
vk::Format::eR32G32B32Sfloat,
sizeof(float) * 5);
// Location 3 : Color
vertices.attributeDescriptions[3] =
vk::VertexInputAttributeDescription(
3,
VERTEX_BUFFER_BIND_ID,
vk::Format::eR32G32B32Sfloat,
sizeof(float) * 8);
vertices.inputState = vk::PipelineVertexInputStateCreateInfo();
vertices.inputState.vertexBindingDescriptionCount =
static_cast<uint32_t>(vertices.bindingDescriptions.size());
vertices.inputState.pVertexBindingDescriptions = vertices.bindingDescriptions.data();
vertices.inputState.vertexAttributeDescriptionCount =
static_cast<uint32_t>(vertices.attributeDescriptions.size());
vertices.inputState.pVertexAttributeDescriptions = vertices.attributeDescriptions.data();
}
void
Texture::setupDescriptorPool()
{
// Example uses one ubo and one image sampler
std::vector<vk::DescriptorPoolSize> poolSizes =
{
{vk::DescriptorType::eUniformBuffer, 1},
{vk::DescriptorType::eCombinedImageSampler, 1}
};
vk::DescriptorPoolCreateInfo descriptorPoolInfo(
{},
2,
static_cast<uint32_t>(poolSizes.size()),
poolSizes.data());
vk::Result res = vkctx.device.createDescriptorPool(&descriptorPoolInfo,
nullptr,
&descriptorPool);
if (res != vk::Result::eSuccess) {
throw bad_vulkan_alloc((int)res, "createDescriptorPool");
}
}
void
Texture::setupDescriptorSetLayout()
{
std::vector<vk::DescriptorSetLayoutBinding> setLayoutBindings =
{
// Binding 0 : Vertex shader uniform buffer
{0,
vk::DescriptorType::eUniformBuffer,
1,
vk::ShaderStageFlagBits::eVertex},
// Binding 1 : Fragment shader image sampler
{1,
vk::DescriptorType::eCombinedImageSampler,
1,
vk::ShaderStageFlagBits::eFragment},
};
vk::DescriptorSetLayoutCreateInfo descriptorLayout(
{},
static_cast<uint32_t>(setLayoutBindings.size()),
setLayoutBindings.data());
vk::Result res
= vkctx.device.createDescriptorSetLayout(&descriptorLayout,
nullptr,
&descriptorSetLayout);
if (res != vk::Result::eSuccess) {
throw bad_vulkan_alloc((int)res, "createDescriptorSetLayout");
}
vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo(
{},
1,
&descriptorSetLayout);
res = vkctx.device.createPipelineLayout(&pipelineLayoutCreateInfo,
nullptr,
&pipelineLayout);
if (res != vk::Result::eSuccess) {
throw bad_vulkan_alloc((int)res, "createPipelineLayout");
}
}
void
Texture::setupDescriptorSet()
{
vk::DescriptorSetAllocateInfo allocInfo(
descriptorPool,
1,
&descriptorSetLayout);
vk::Result res
= vkctx.device.allocateDescriptorSets(&allocInfo, &descriptorSet);
if (res != vk::Result::eSuccess) {
throw bad_vulkan_alloc((int)res, "allocateDescriptorSets");
}
// Image descriptor for the color map texture
vk::DescriptorImageInfo texDescriptor(
sampler,
imageView,
vk::ImageLayout::eShaderReadOnlyOptimal);
std::vector<vk::WriteDescriptorSet> writeDescriptorSets;
// Binding 0 : Vertex shader uniform buffer
writeDescriptorSets.push_back(vk::WriteDescriptorSet(
descriptorSet,
0,
0,
1,
vk::DescriptorType::eUniformBuffer,
nullptr,
&uniformDataVS.descriptor)
);
// Binding 1 : Fragment shader texture sampler
writeDescriptorSets.push_back(vk::WriteDescriptorSet(
descriptorSet,
1,
0,
1,
vk::DescriptorType::eCombinedImageSampler,
&texDescriptor)
);
vkctx.device.updateDescriptorSets(
static_cast<uint32_t>(writeDescriptorSets.size()),
writeDescriptorSets.data(),
0,
nullptr);
}
void
Texture::preparePipelines()
{
vk::PipelineInputAssemblyStateCreateInfo inputAssemblyState(
{},
vk::PrimitiveTopology::eTriangleStrip,
// primmitiveRestartEnable not needed but disabling it results in a MoltenVK
// feature not present warning.
true);
vk::PipelineRasterizationStateCreateInfo rasterizationState;
// Must be false because we haven't enabled the depthClamp device feature.
rasterizationState.depthClampEnable = false;
rasterizationState.rasterizerDiscardEnable = false;
rasterizationState.polygonMode = vk::PolygonMode::eFill;
rasterizationState.cullMode = vk::CullModeFlagBits::eNone;
rasterizationState.frontFace = vk::FrontFace::eCounterClockwise;
rasterizationState.lineWidth = 1.0f;
vk::PipelineColorBlendAttachmentState blendAttachmentState;
blendAttachmentState.blendEnable = false;
//blendAttachmentState.colorWriteMask = 0xf;
blendAttachmentState.colorWriteMask = vk::ColorComponentFlagBits::eR
| vk::ColorComponentFlagBits::eG
| vk::ColorComponentFlagBits::eB
| vk::ColorComponentFlagBits::eA;
vk::PipelineColorBlendStateCreateInfo colorBlendState;
colorBlendState.attachmentCount = 1;
colorBlendState.pAttachments = &blendAttachmentState;
vk::PipelineDepthStencilStateCreateInfo depthStencilState;
depthStencilState.depthTestEnable = true;
depthStencilState.depthWriteEnable = true;
depthStencilState.depthCompareOp = vk::CompareOp::eLessOrEqual;
vk::PipelineViewportStateCreateInfo viewportState;
viewportState.viewportCount = 1;
viewportState.scissorCount = 1;
vk::PipelineMultisampleStateCreateInfo multisampleState;
multisampleState.rasterizationSamples = vk::SampleCountFlagBits::e1;
std::vector<vk::DynamicState> dynamicStateEnables = {
vk::DynamicState::eViewport,
vk::DynamicState::eScissor
};
vk::PipelineDynamicStateCreateInfo dynamicState(
{},
static_cast<uint32_t>(dynamicStateEnables.size()),
dynamicStateEnables.data());
// Load shaders
std::array<vk::PipelineShaderStageCreateInfo,2> shaderStages;
std::string filepath = getAssetPath();
shaderStages[0] = loadShader(filepath + "texture.vert.spv",
vk::ShaderStageFlagBits::eVertex);
std::string fragShader = filepath;
if (texture.viewType == VK_IMAGE_VIEW_TYPE_1D)
fragShader += "texture1d";
else
fragShader += "texture2d";
fragShader += ".frag.spv";
shaderStages[1] = loadShader(fragShader,
vk::ShaderStageFlagBits::eFragment);
vk::GraphicsPipelineCreateInfo pipelineCreateInfo;
pipelineCreateInfo.layout = pipelineLayout;
pipelineCreateInfo.renderPass = vkctx.renderPass;
pipelineCreateInfo.pVertexInputState = &vertices.inputState;
pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
pipelineCreateInfo.pRasterizationState = &rasterizationState;
pipelineCreateInfo.pColorBlendState = &colorBlendState;
pipelineCreateInfo.pMultisampleState = &multisampleState;
pipelineCreateInfo.pViewportState = &viewportState;
pipelineCreateInfo.pDepthStencilState = &depthStencilState;
pipelineCreateInfo.pDynamicState = &dynamicState;
pipelineCreateInfo.stageCount = (uint32_t)shaderStages.size();
pipelineCreateInfo.pStages = shaderStages.data();
vk::Result res
= vkctx.device.createGraphicsPipelines(vkctx.pipelineCache, 1,
&pipelineCreateInfo, nullptr,
&pipelines.solid);
if (res != vk::Result::eSuccess) {
throw bad_vulkan_alloc((int)res, "createGraphicsPipelines");
}
}
// Prepare and initialize uniform buffer containing shader uniforms
void
Texture::prepareUniformBuffers()
{
// Vertex shader uniform buffer block
vkctx.createBuffer(
vk::BufferUsageFlagBits::eUniformBuffer,
sizeof(uboVS),
&uboVS,
&uniformDataVS.buffer,
&uniformDataVS.memory,
&uniformDataVS.descriptor);
updateUniformBuffers();
}
void
Texture::updateUniformBuffers()
{
if (w_width == 0 || w_height == 0)
return;
// Vertex shader
uboVS.projection = glm::perspective(glm::radians(60.0f), (float)w_width / (float)w_height, 0.001f, 256.0f);
glm::mat4 viewMatrix = glm::translate(glm::mat4(), glm::vec3(0.0f, 0.0f, zoom));
uboVS.model = viewMatrix * glm::translate(glm::mat4(), cameraPos);
uboVS.model = glm::rotate(uboVS.model, glm::radians(rotation.x), glm::vec3(1.0f, 0.0f, 0.0f));
uboVS.model = glm::rotate(uboVS.model, glm::radians(rotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
uboVS.model = glm::rotate(uboVS.model, glm::radians(rotation.z), glm::vec3(0.0f, 0.0f, 1.0f));
// Because MetalSL does not have a matrix inverse function...
// It looks like the glm::mat3(glm::mat4) does something different than
// GLSL. If I convert to mat3 here, only half the quad is lit. Do it in
// the shader.
uboVS.normal = inverse(transpose(uboVS.model));
uboVS.viewPos = glm::vec4(0.0f, 0.0f, -zoom, 0.0f);
uint8_t *pData;
VK_CHECK_RESULT(vkMapMemory(vkctx.device, uniformDataVS.memory, 0, sizeof(uboVS), 0, (void **)&pData));
memcpy(pData, &uboVS, sizeof(uboVS));
vkUnmapMemory(vkctx.device, uniformDataVS.memory);
}
void
Texture::prepareSamplerAndView()
{
// Create sampler.
vk::SamplerCreateInfo samplerInfo;
// Set the non-default values
samplerInfo.magFilter = vk::Filter::eLinear;
samplerInfo.minFilter = vk::Filter::eLinear;
samplerInfo.mipmapMode = vk::SamplerMipmapMode::eLinear;
samplerInfo.maxLod = (float)texture.levelCount;
if (vkctx.gpuFeatures.samplerAnisotropy == VK_TRUE) {
samplerInfo.anisotropyEnable = VK_TRUE;
samplerInfo.maxAnisotropy = 8;
} else {
// vulkan.hpp needs fixing
samplerInfo.maxAnisotropy = 1.0;
}
samplerInfo.borderColor = vk::BorderColor::eFloatOpaqueWhite;
sampler = vkctx.device.createSampler(samplerInfo);
// Create image view.
// Textures are not directly accessed by the shaders and are abstracted
// by image views containing additional information and sub resource
// ranges.
vk::ImageViewCreateInfo viewInfo;
// Set the non-default values.
viewInfo.components = swizzle;
viewInfo.image = texture.image;
viewInfo.format = static_cast<vk::Format>(texture.imageFormat);
viewInfo.viewType = static_cast<vk::ImageViewType>(texture.viewType);
viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
viewInfo.subresourceRange.layerCount = texture.layerCount;
viewInfo.subresourceRange.levelCount = texture.levelCount;
imageView = vkctx.device.createImageView(viewInfo);
}
void
Texture::prepare()
{
prepareSamplerAndView();
generateQuad();
setupVertexDescriptions();
prepareUniformBuffers();
setupDescriptorSetLayout();
preparePipelines();
setupDescriptorPool();
setupDescriptorSet();
vkctx.createDrawCommandBuffers();
buildCommandBuffers();
}
void
Texture::changeLodBias(float delta)
{
uboVS.lodBias += delta;
if (uboVS.lodBias < 0.0f)
{
uboVS.lodBias = 0.0f;
}
if (uboVS.lodBias > texture.levelCount)
{
uboVS.lodBias = (float)texture.levelCount;
}
updateUniformBuffers();
//updateTextOverlay();
}
void
Texture::keyPressed(uint32_t keyCode)
{
switch (keyCode)
{
case SDLK_KP_PLUS:
changeLodBias(0.1f);
break;
case SDLK_KP_MINUS:
changeLodBias(-0.1f);
break;
}
}
void
Texture::getOverlayText(VulkanTextOverlay *textOverlay, float yOffset)
{
std::stringstream ss;
ss << std::setprecision(2) << std::fixed << uboVS.lodBias;
textOverlay->addText("LOD bias: " + ss.str() + " (numpad +/- to change)",
5.0f, yOffset, VulkanTextOverlay::alignLeft);
}
const char*
Texture::customizeTitle(const char* const baseTitle)
{
if (transcoded) {
this->title = baseTitle;
this->title += " Transcoded to ";
this->title += vkFormatString((VkFormat)transcodedFormat);
return this->title.c_str();
}
return baseTitle;
}
+111
View File
@@ -0,0 +1,111 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2017-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
#include <vector>
#include "VulkanLoadTestSample.h"
#include <glm/gtc/matrix_transform.hpp>
class Texture : public VulkanLoadTestSample
{
public:
Texture(VulkanContext& vkctx,
uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath);
~Texture();
virtual void resize(uint32_t width, uint32_t height);
virtual void run(uint32_t msTicks);
virtual void getOverlayText(VulkanTextOverlay *textOverlay, float yOffset);
virtual const char* customizeTitle(const char* const title);
static VulkanLoadTestSample*
create(VulkanContext& vkctx,
uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath);
protected:
enum class UseSuballocator
{
No = 0,
Yes
};
std::string filename;
ktxVulkanTexture texture;
vk::Sampler sampler;
vk::ImageView imageView;
vk::ImageTiling tiling;
UseSuballocator useSubAlloc;
vk::ComponentMapping swizzle;
struct {
vk::PipelineVertexInputStateCreateInfo inputState;
std::vector<vk::VertexInputBindingDescription> bindingDescriptions;
std::vector<vk::VertexInputAttributeDescription> attributeDescriptions;
} vertices;
MeshBuffer quad;
typedef std::array<float, 3> rgbcolor;
std::array<rgbcolor,4> quadColor;
UniformData uniformDataVS;
struct {
glm::mat4 projection;
glm::mat4 model;
glm::mat4 normal;
glm::vec4 viewPos;
float lodBias = 0.0f;
} uboVS;
struct {
vk::Pipeline solid;
} pipelines;
vk::PipelineLayout pipelineLayout;
vk::DescriptorSet descriptorSet;
vk::DescriptorSetLayout descriptorSetLayout;
vk::DescriptorPool descriptorPool;
int sign_s = 1;
int sign_t = 1;
bool transcoded;
vk::Format transcodedFormat;
std::string title;
void cleanup();
void buildCommandBuffers();
void generateQuad();
void setupVertexDescriptions();
void setupDescriptorPool();
void setupDescriptorSetLayout();
void setupDescriptorSet();
void preparePipelines();
// Prepare and initialize uniform buffer containing shader uniforms
void prepareUniformBuffers();
void updateUniformBuffers();
void prepareSamplerAndView();
void prepare();
void processArgs(std::string sArgs);
virtual void keyPressed(uint32_t keyCode);
virtual void viewChanged()
{
updateUniformBuffers();
}
void changeLodBias(float delta);
};
@@ -0,0 +1,111 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2017-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @class Texture3d
* @~English
*
* @brief Definition of test sample for loading and displaying the slices of a 3d texture.
*
* @author Mark Callow, github.com/MarkCallow.
*
* @par Acknowledgement
* Thanks to Sascha Willems' - www.saschawillems.de - for the concept,
* the VulkanTextOverlay class and the shaders used by this test.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <algorithm>
#include <time.h>
#include <vector>
#include "Texture3d.h"
#include "ltexceptions.h"
VulkanLoadTestSample*
Texture3d::create(VulkanContext& vkctx,
uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath)
{
return new Texture3d(vkctx, width, height, szArgs, sBasePath);
}
#define INSTANCE_COUNT_CONST_ID 1
#define INSTANCES_DECLARED_IN_SHADER 30
Texture3d::Texture3d(VulkanContext& vkctx,
uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath)
: InstancedSampleBase(vkctx, width, height, szArgs, sBasePath)
{
zoom = -15.0f;
if (texture.depth == 1) {
std::stringstream message;
message << "Texture3d requires a 3d texture.";
throw std::runtime_error(message.str());
}
try {
prepare("instancing3d.frag.spv", "instancing3d.vert.spv",
INSTANCE_COUNT_CONST_ID, texture.depth,
INSTANCES_DECLARED_IN_SHADER);
} catch (std::exception& e) {
(void)e; // To quiet unused variable warnings from some compilers.
//cleanup(); // See explanation in TextureMipmap.cpp
throw;
}
}
#if 0
// Addition of extra uniform buffer for the instance count is to work around
// MoltenVK issue #issue 1421:
// https://github.com/KhronosGroup/MoltenVK/issues/1421.
void
Texture3d::addSubclassDescriptors(DescriptorBindings& descriptorBindings)
{
descriptorBindings.push_back(vk::DescriptorSetLayoutBinding(
2, // Binding 2 : uniform buffer for instanceCount value.
vk::DescriptorType::eUniformBuffer,
1,
vk::ShaderStageFlagBits::eVertex
));
}
#endif
// Providing instanceCount via a push constant is a workaround for
// MoltenVK issue #1421:
// https://github.com/KhronosGroup/MoltenVK/issues/1421.
void
Texture3d::addSubclassPushConstantRanges(PushConstantRanges& ranges)
{
ranges.push_back(vk::PushConstantRange(
vk::ShaderStageFlagBits::eVertex,
0, // offset
sizeof(instanceCount)
));
}
void
Texture3d::setSubclassPushConstants(uint32_t bufferIndex)
{
vkCmdPushConstants(
vkctx.drawCmdBuffers[bufferIndex],
pipelineLayout,
VK_SHADER_STAGE_VERTEX_BIT,
0,
sizeof(instanceCount),
&instanceCount
);
}
@@ -0,0 +1,46 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2017-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _TEXTURE_3D_H_
#define _TEXTURE_3D_H_
/**
* @internal
* @file
* @~English
*
* @brief Declaration of test sample for loading and displaying the slices of a 3d texture..
*
* @author Mark Callow, github.com/MarkCallow.
*/
#include <vector>
#include "InstancedSampleBase.h"
#include <glm/gtc/matrix_transform.hpp>
class Texture3d : public InstancedSampleBase
{
public:
Texture3d(VulkanContext& vkctx,
uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath);
static VulkanLoadTestSample*
create(VulkanContext& vkctx,
uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath);
protected:
virtual void addSubclassPushConstantRanges(PushConstantRanges&);
virtual void setSubclassPushConstants(uint32_t bufferIndex);
};
#endif /* _TEXTURE_3D_H_ */
@@ -0,0 +1,71 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2017-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @class TextureArray
* @~English
*
* @brief Definition of test sample for loading and displaying the layers of a 2D array texture.
*
* @author Mark Callow, github.com/MarkCallow.
*
* @par Acknowledgement
* Thanks to Sascha Willems' - www.saschawillems.de - for the concept,
* the VulkanTextOverlay class and the shaders used by this test.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <algorithm>
#include <time.h>
#include <vector>
#include "TextureArray.h"
#include "ltexceptions.h"
VulkanLoadTestSample*
TextureArray::create(VulkanContext& vkctx,
uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath)
{
return new TextureArray(vkctx, width, height, szArgs, sBasePath);
}
#define INSTANCE_COUNT_CONST_ID 1
#define INSTANCES_DECLARED_IN_SHADER 30
TextureArray::TextureArray(VulkanContext& vkctx,
uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath)
: InstancedSampleBase(vkctx, width, height, szArgs, sBasePath)
{
zoom = -15.0f;
if (texture.layerCount == 1) {
std::stringstream message;
message << "TextureArray requires an array texture.";
throw std::runtime_error(message.str());
}
try {
prepare("instancing.frag.spv", "instancing.vert.spv",
INSTANCE_COUNT_CONST_ID, texture.layerCount,
INSTANCES_DECLARED_IN_SHADER);
} catch (std::exception& e) {
(void)e; // To quiet unused variable warnings from some compilers.
//cleanup(); // See explanation in TextureMipmap.cpp
throw;
}
}
@@ -0,0 +1,42 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2017-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _TEXTURE_ARRAY_H_
#define _TEXTURE_ARRAY_H_
/**
* @internal
* @file
* @~English
*
* @brief Declaration of test sample for loading and displaying the layers of a 2D array texture.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#include <vector>
#include "InstancedSampleBase.h"
#include <glm/gtc/matrix_transform.hpp>
class TextureArray : public InstancedSampleBase
{
public:
TextureArray(VulkanContext& vkctx,
uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath);
static VulkanLoadTestSample*
create(VulkanContext& vkctx,
uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath);
};
#endif /* _TEXTURE_ARRAY_H_ */
@@ -0,0 +1,871 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2017-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @class TextureCubemap
* @~English
*
* @brief Test loading of cubemap textures.
*
* @author Mark Callow, github.com/MarkCallow.
*
* @par Acknowledgement
* Thanks to Sascha Willems' - www.saschawillems.de - for the concept,
* the VulkanTextOverlay VulkanMeshLoader classes and the shaders used
* by this test.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <vector>
#include "TextureCubemap.h"
#include "VulkanTextureTranscoder.hpp"
#include "SwipeDetector.h"
#include "argparser.h"
#include "ltexceptions.h"
#define VERTEX_BUFFER_BIND_ID 0
#define ENABLE_VALIDATION false
#define USE_GL_RH_NDC 0
#define NORMALIZE_AXES 0
// Vertex layout for this example
std::vector<vkMeshLoader::VertexLayout> vertexLayout =
{
vkMeshLoader::VERTEX_LAYOUT_POSITION,
vkMeshLoader::VERTEX_LAYOUT_NORMAL,
vkMeshLoader::VERTEX_LAYOUT_UV
};
VulkanLoadTestSample*
TextureCubemap::create(VulkanContext& vkctx,
uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath)
{
return new TextureCubemap(vkctx, width, height, szArgs, sBasePath,
USE_GL_RH_NDC ? 1 : -1);
}
TextureCubemap::TextureCubemap(VulkanContext& vkctx,
uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath,
int32_t yflip)
: VulkanLoadTestSample(vkctx, width, height, sBasePath, yflip)
{
zoom = -4.0f;
rotationSpeed = 0.25f;
rotation = { -7.25f, 120.0f, 0.0f };
transcoded = false;
ktxVulkanDeviceInfo vdi;
ktxVulkanDeviceInfo_Construct(&vdi, vkctx.gpu, vkctx.device,
vkctx.queue, vkctx.commandPool, nullptr);
processArgs(szArgs);
KTX_error_code ktxresult;
ktxTexture* kTexture;
std::string ktxfilepath = externalFile ? ktxfilename
: getAssetPath() + ktxfilename;
ktxresult = ktxTexture_CreateFromNamedFile(ktxfilepath.c_str(),
preloadImages ? KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT
: KTX_TEXTURE_CREATE_NO_FLAGS,
&kTexture);
if (KTX_SUCCESS != ktxresult) {
std::stringstream message;
message << "Creation of ktxTexture from \"" << ktxfilepath
<< "\" failed: " << ktxErrorString(ktxresult);
throw std::runtime_error(message.str());
}
if (ktxTexture_NeedsTranscoding(kTexture)) {
TextureTranscoder tc(vkctx);
tc.transcode((ktxTexture2*)kTexture);
transcoded = true;
}
vk::Format
vkFormat = static_cast<vk::Format>(ktxTexture_GetVkFormat(kTexture));
transcodedFormat = vkFormat;
vk::FormatProperties properties;
vkctx.gpu.getFormatProperties(vkFormat, &properties);
vk::FormatFeatureFlags wantedFeatures =
vk::FormatFeatureFlagBits::eSampledImage
| vk::FormatFeatureFlagBits::eSampledImageFilterLinear;
if (!(properties.optimalTilingFeatures & wantedFeatures)) {
ktxTexture_Destroy(kTexture);
throw unsupported_ttype();
}
ktxresult = ktxTexture_VkUpload(kTexture, &vdi, &cubeMap);
if (KTX_SUCCESS != ktxresult) {
std::stringstream message;
message << "ktxTexture_VkUpload failed: " << ktxErrorString(ktxresult);
throw std::runtime_error(message.str());
}
if (kTexture->orientation.y == KTX_ORIENT_Y_DOWN) {
// Assume a KTX-compliant cubemap. That means the faces are in a
// LH coord system with +y up. Multiply the cube's y and z by -1 to
// put the +z face in front of the view and keep +y up. Alternatively
// we could multiply the y and x coords by -1 to keep the +y up while
// placing the +z face in the +z direction.
#if !USE_GL_RH_NDC
ubo.uvwTransform = glm::scale(glm::mat4(1.0f), glm::vec3(1, -1, -1));
#else
// Scale the skybox cube's z by -1 to convert it to LH coords
// with the +z face in front of the view.
ubo.uvwTransform = glm::scale(glm::mat4(1.0f), glm::vec3(1, 1, -1));
#endif
} else {
std::stringstream message;
message << "Cubemap faces have unsupported KTXorientation value.";
throw std::runtime_error(message.str());
}
ktxTexture_Destroy(kTexture);
ktxVulkanDeviceInfo_Destruct(&vdi);
try {
prepare();
} catch (std::exception& e) {
(void)e; // To quiet unused variable warnings from some compilers.
cleanup();
throw;
}
}
TextureCubemap::~TextureCubemap()
{
cleanup();
}
int
TextureCubemap::doEvent(SDL_Event* event)
{
int result = 0;
switch(event->type) {
case SDL_EVENT_USER:
if (event->user.code == SwipeDetector::swipeGesture) {
SwipeDetector::Direction direction
= SwipeDetector::pointerToDirection(event->user.data1);
switch (direction) {
case SwipeDetector::Direction::up:
toggleObject(+1);
break;
case SwipeDetector::Direction::down:
toggleObject(-1);
break;
default:
result = 1;
}
} else {
result = 1;
}
break;
default:
result = 1;
}
if (result == 1)
result = VulkanLoadTestSample::doEvent(event);
return result;
}
void
TextureCubemap::resize(uint32_t width, uint32_t height)
{
this->w_width = width;
this->w_height = height;
rebuildCommandBuffers();
updateUniformBuffers();
}
void
TextureCubemap::run(uint32_t /*msTicks*/)
{
// Nothing to do since the scene is not animated.
// VulkanLoadTests base class redraws from the command buffer we built.
}
//===================================================================
void
TextureCubemap::processArgs(std::string sArgs)
{
// Options descriptor
struct argparser::option longopts[] = {
{"external", argparser::option::no_argument, &externalFile, 1},
{"preload", argparser::option::no_argument, &preloadImages, 1},
{NULL, argparser::option::no_argument, NULL, 0}
};
argvector argv(sArgs);
argparser ap(argv);
int ch;
while ((ch = ap.getopt(nullptr, longopts, nullptr)) != -1) {
switch (ch) {
case 0: break;
default: assert(false); // Error in args in sample table.
}
}
assert(ap.optind < argv.size());
ktxfilename = argv[ap.optind];
}
/* ------------------------------------------------------------------------- */
void
TextureCubemap::cleanup()
{
// Clean up used Vulkan resources
// Clean up texture resources
if (sampler)
vkctx.device.destroySampler(sampler);
if (imageView)
vkctx.device.destroyImageView(imageView);
ktxVulkanTexture_Destruct(&cubeMap, vkctx.device, nullptr);
if (pipelines.reflect)
vkctx.device.destroyPipeline(pipelines.reflect);
if (pipelines.skybox)
vkctx.device.destroyPipeline(pipelines.skybox);
if (pipelineLayout)
vkctx.device.destroyPipelineLayout(pipelineLayout);
if (descriptorSetLayout)
vkctx.device.destroyDescriptorSetLayout(descriptorSetLayout);
vkctx.destroyDrawCommandBuffers();
for (size_t i = 0; i < meshes.objects.size(); i++)
{
vkMeshLoader::freeMeshBufferResources(vkctx.device, &meshes.objects[i]);
}
vkMeshLoader::freeMeshBufferResources(vkctx.device, &meshes.skybox);
uniformData.object.freeResources(vkctx.device);
uniformData.skybox.freeResources(vkctx.device);
}
void
TextureCubemap::rebuildCommandBuffers()
{
if (!vkctx.checkDrawCommandBuffers())
{
vkctx.destroyDrawCommandBuffers();
vkctx.createDrawCommandBuffers();
}
buildCommandBuffers();
}
void
TextureCubemap::buildCommandBuffers()
{
vk::CommandBufferBeginInfo cmdBufInfo({}, nullptr);
vk::ClearValue clearValues[2];
clearValues[0].color = defaultClearColor;
clearValues[1].depthStencil = vk::ClearDepthStencilValue(1.0f, 0);
vk::RenderPassBeginInfo renderPassBeginInfo(vkctx.renderPass,
nullptr,
{{0, 0}, {w_width, w_height}},
2,
clearValues);
for (uint32_t i = 0; i < vkctx.drawCmdBuffers.size(); ++i)
{
// Set target frame buffer
renderPassBeginInfo.framebuffer = vkctx.framebuffers[i];
VK_CHECK_RESULT(vkBeginCommandBuffer(vkctx.drawCmdBuffers[i],
&static_cast<const VkCommandBufferBeginInfo&>(cmdBufInfo)));
vkCmdBeginRenderPass(vkctx.drawCmdBuffers[i],
&static_cast<const VkRenderPassBeginInfo&>(renderPassBeginInfo),
VK_SUBPASS_CONTENTS_INLINE);
#if !USE_GL_RH_NDC
vk::Viewport viewport(0, 0,
(float)w_width, (float)w_height,
0.0f, 1.0f);
#else
// Make OpenGL style viewport
vk::Viewport viewport(0, (float)w_height,
(float)w_width, -(float)w_height,
0.0f, 1.0f);
#endif
vkCmdSetViewport(vkctx.drawCmdBuffers[i], 0, 1,
&static_cast<const VkViewport&>(viewport));
vk::Rect2D scissor({0, 0}, {w_width, w_height});
vkCmdSetScissor(vkctx.drawCmdBuffers[i], 0, 1,
&static_cast<const VkRect2D&>(scissor));
VkDeviceSize offsets[1] = { 0 };
// Skybox
if (displaySkybox)
{
vkCmdBindDescriptorSets(vkctx.drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS,
pipelineLayout, 0, 1,
&static_cast<const VkDescriptorSet&>(descriptorSets.skybox), 0, NULL);
vkCmdBindVertexBuffers(vkctx.drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &meshes.skybox.vertices.buf, offsets);
vkCmdBindIndexBuffer(vkctx.drawCmdBuffers[i], meshes.skybox.indices.buf, 0, VK_INDEX_TYPE_UINT32);
vkCmdBindPipeline(vkctx.drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.skybox);
vkCmdDrawIndexed(vkctx.drawCmdBuffers[i], meshes.skybox.indexCount, 1, 0, 0, 0);
}
// 3D object
vkCmdBindDescriptorSets(vkctx.drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS,
pipelineLayout, 0, 1,
&static_cast<const VkDescriptorSet&>(descriptorSets.object), 0, NULL);
vkCmdBindVertexBuffers(vkctx.drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &meshes.objects[meshes.objectIndex].vertices.buf, offsets);
vkCmdBindIndexBuffer(vkctx.drawCmdBuffers[i], meshes.objects[meshes.objectIndex].indices.buf, 0, VK_INDEX_TYPE_UINT32);
vkCmdBindPipeline(vkctx.drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.reflect);
vkCmdDrawIndexed(vkctx.drawCmdBuffers[i], meshes.objects[meshes.objectIndex].indexCount, 1, 0, 0, 0);
vkCmdEndRenderPass(vkctx.drawCmdBuffers[i]);
VK_CHECK_RESULT(vkEndCommandBuffer(vkctx.drawCmdBuffers[i]));
}
}
void
TextureCubemap::loadMeshes()
{
std::string filepath = getAssetPath();
// Skybox
loadMesh(filepath + "cube.obj", &meshes.skybox, vertexLayout, 0.05f);
// Objects
meshes.objects.resize(3);
loadMesh(filepath + "sphere.obj", &meshes.objects[0], vertexLayout, 0.05f);
loadMesh(filepath + "teapot.dae", &meshes.objects[1], vertexLayout, 0.05f);
loadMesh(filepath + "torusknot.obj", &meshes.objects[2], vertexLayout, 0.05f);
}
void
TextureCubemap::setupVertexDescriptions()
{
// Binding description
vertices.bindingDescriptions.resize(1);
vertices.bindingDescriptions[0] =
vk::VertexInputBindingDescription(
VERTEX_BUFFER_BIND_ID,
vkMeshLoader::vertexSize(vertexLayout),
vk::VertexInputRate::eVertex);
// Attribute descriptions
// Describes memory layout and shader positions
vertices.attributeDescriptions.resize(3);
// Location 0 : Position
vertices.attributeDescriptions[0] =
vk::VertexInputAttributeDescription(
0,
VERTEX_BUFFER_BIND_ID,
vk::Format::eR32G32B32Sfloat,
0);
// Location 1 : Vertex normal
vertices.attributeDescriptions[1] =
vk::VertexInputAttributeDescription(
1,
VERTEX_BUFFER_BIND_ID,
vk::Format::eR32G32B32Sfloat,
sizeof(float) * 3);
// Location 2 : Texture coordinates
vertices.attributeDescriptions[2] =
vk::VertexInputAttributeDescription(
2,
VERTEX_BUFFER_BIND_ID,
vk::Format::eR32G32Sfloat,
sizeof(float) * 6);
vertices.inputState = vk::PipelineVertexInputStateCreateInfo();
vertices.inputState.vertexBindingDescriptionCount =
static_cast<uint32_t>(vertices.bindingDescriptions.size());
vertices.inputState.pVertexBindingDescriptions = vertices.bindingDescriptions.data();
vertices.inputState.vertexAttributeDescriptionCount =
static_cast<uint32_t>(vertices.attributeDescriptions.size());
vertices.inputState.pVertexAttributeDescriptions = vertices.attributeDescriptions.data();
}
void
TextureCubemap::setupDescriptorPool()
{
// Example uses one ubo and one image sampler
std::vector<vk::DescriptorPoolSize> poolSizes =
{
{vk::DescriptorType::eUniformBuffer, 2},
{vk::DescriptorType::eCombinedImageSampler, 2}
};
vk::DescriptorPoolCreateInfo descriptorPoolInfo(
{},
2,
static_cast<uint32_t>(poolSizes.size()),
poolSizes.data());
vk::Result res = vkctx.device.createDescriptorPool(&descriptorPoolInfo,
nullptr,
&descriptorPool);
if (res != vk::Result::eSuccess) {
throw bad_vulkan_alloc((int)res, "createDescriptorPool");
}
}
void
TextureCubemap::setupDescriptorSetLayout()
{
std::vector<vk::DescriptorSetLayoutBinding> setLayoutBindings =
{
// Binding 0 : Vertex shader uniform buffer
{
0,
vk::DescriptorType::eUniformBuffer,
1,
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment,
},
// Binding 1 : Fragment shader image sampler
{
1,
vk::DescriptorType::eCombinedImageSampler,
1,
vk::ShaderStageFlagBits::eFragment
},
};
vk::DescriptorSetLayoutCreateInfo descriptorLayout(
{},
static_cast<uint32_t>(setLayoutBindings.size()),
setLayoutBindings.data());
vk::Result res
= vkctx.device.createDescriptorSetLayout(&descriptorLayout,
nullptr,
&descriptorSetLayout);
if (res != vk::Result::eSuccess) {
throw bad_vulkan_alloc((int)res, "createDescriptorSetLayout");
}
vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo(
{},
1,
&descriptorSetLayout);
res = vkctx.device.createPipelineLayout(&pipelineLayoutCreateInfo,
nullptr,
&pipelineLayout);
if (res != vk::Result::eSuccess) {
throw bad_vulkan_alloc((int)res, "createPipelineLayout");
}
}
void
TextureCubemap::setupDescriptorSets()
{
vk::DescriptorSetAllocateInfo allocInfo(
descriptorPool,
1,
&descriptorSetLayout);
vk::Result res
= vkctx.device.allocateDescriptorSets(&allocInfo,
&descriptorSets.object);
if (res != vk::Result::eSuccess) {
throw bad_vulkan_alloc((int)res, "allocateDescriptorSets");
}
// Image descriptor for the cubemap texture
vk::DescriptorImageInfo cubeMapDescriptor(
sampler,
imageView,
vk::ImageLayout::eShaderReadOnlyOptimal);
std::vector<vk::WriteDescriptorSet> writeDescriptorSets =
{
// Binding 0 : Vertex shader uniform buffer
vk::WriteDescriptorSet(
descriptorSets.object,
0,
0,
1,
vk::DescriptorType::eUniformBuffer,
nullptr,
&uniformData.object.descriptor),
// Binding 1 : Fragment shader cubemap sampler
vk::WriteDescriptorSet(
descriptorSets.object,
1,
0,
1,
vk::DescriptorType::eCombinedImageSampler,
&cubeMapDescriptor)
};
vkctx.device.updateDescriptorSets(
static_cast<uint32_t>(writeDescriptorSets.size()),
writeDescriptorSets.data(),
0,
nullptr);
// Sky box descriptor set
res = vkctx.device.allocateDescriptorSets(&allocInfo,
&descriptorSets.skybox);
if (res != vk::Result::eSuccess) {
throw bad_vulkan_alloc((int)res, "allocateDescriptorSets");
}
writeDescriptorSets =
{
// Binding 0 : Vertex shader uniform buffer
vk::WriteDescriptorSet(
descriptorSets.skybox,
0,
0,
1,
vk::DescriptorType::eUniformBuffer,
nullptr,
&uniformData.skybox.descriptor),
// Binding 1 : Fragment shader texture sampler
vk::WriteDescriptorSet(
descriptorSets.skybox,
1,
0,
1,
vk::DescriptorType::eCombinedImageSampler,
&cubeMapDescriptor)
};
vkctx.device.updateDescriptorSets(
static_cast<uint32_t>(writeDescriptorSets.size()),
writeDescriptorSets.data(),
0,
nullptr);
}
void
TextureCubemap::preparePipelines()
{
vk::PipelineInputAssemblyStateCreateInfo inputAssemblyState(
{},
vk::PrimitiveTopology::eTriangleList);
vk::PipelineRasterizationStateCreateInfo rasterizationState;
// Must be false because we haven't enabled the depthClamp device feature.
rasterizationState.depthClampEnable = false;
rasterizationState.rasterizerDiscardEnable = VK_FALSE;
rasterizationState.polygonMode = vk::PolygonMode::eFill;
rasterizationState.cullMode = vk::CullModeFlagBits::eBack;
// Make the faces on the inside of the cube the front faces. The mesh
// was designed with the exterior faces as the front faces for OpenGL's
// default of GL_CCW.
#if !USE_GL_RH_NDC
rasterizationState.frontFace = vk::FrontFace::eCounterClockwise;
#else
rasterizationState.frontFace = vk::FrontFace::eClockwise;
#endif
rasterizationState.lineWidth = 1.0f;
vk::PipelineColorBlendAttachmentState blendAttachmentState;
blendAttachmentState.blendEnable = VK_FALSE;
//blendAttachmentState.colorWriteMask = 0xf;
blendAttachmentState.colorWriteMask = vk::ColorComponentFlagBits::eR
| vk::ColorComponentFlagBits::eG
| vk::ColorComponentFlagBits::eB
| vk::ColorComponentFlagBits::eA;
vk::PipelineColorBlendStateCreateInfo colorBlendState;
colorBlendState.attachmentCount = 1;
colorBlendState.pAttachments = &blendAttachmentState;
vk::PipelineDepthStencilStateCreateInfo depthStencilState;
depthStencilState.depthTestEnable = VK_FALSE;
depthStencilState.depthWriteEnable = VK_FALSE;
depthStencilState.depthCompareOp = vk::CompareOp::eLessOrEqual;
vk::PipelineViewportStateCreateInfo viewportState;
viewportState.viewportCount = 1;
viewportState.scissorCount = 1;
vk::PipelineMultisampleStateCreateInfo multisampleState;
multisampleState.rasterizationSamples = vk::SampleCountFlagBits::e1;
std::vector<vk::DynamicState> dynamicStateEnables = {
vk::DynamicState::eViewport,
vk::DynamicState::eScissor
};
vk::PipelineDynamicStateCreateInfo dynamicState(
{},
static_cast<uint32_t>(dynamicStateEnables.size()),
dynamicStateEnables.data());
// Load shaders
std::array<vk::PipelineShaderStageCreateInfo,2> shaderStages;
std::string filepath = getAssetPath();
shaderStages[0] = loadShader(filepath + "skybox.vert.spv",
vk::ShaderStageFlagBits::eVertex);
shaderStages[1] = loadShader(filepath + "skybox.frag.spv",
vk::ShaderStageFlagBits::eFragment);
vk::GraphicsPipelineCreateInfo pipelineCreateInfo;
pipelineCreateInfo.layout = pipelineLayout;
pipelineCreateInfo.renderPass = vkctx.renderPass;
pipelineCreateInfo.pVertexInputState = &vertices.inputState;
pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
pipelineCreateInfo.pRasterizationState = &rasterizationState;
pipelineCreateInfo.pColorBlendState = &colorBlendState;
pipelineCreateInfo.pMultisampleState = &multisampleState;
pipelineCreateInfo.pViewportState = &viewportState;
pipelineCreateInfo.pDepthStencilState = &depthStencilState;
pipelineCreateInfo.pDynamicState = &dynamicState;
pipelineCreateInfo.stageCount = (uint32_t)shaderStages.size();
pipelineCreateInfo.pStages = shaderStages.data();
vk::Result res
= vkctx.device.createGraphicsPipelines(vkctx.pipelineCache, 1,
&pipelineCreateInfo, nullptr,
&pipelines.skybox);
if (res != vk::Result::eSuccess) {
throw bad_vulkan_alloc((int)res, "createGraphicsPipelines");
}
// Cube map reflect pipeline
shaderStages[0] = loadShader(filepath + "reflect.vert.spv",
vk::ShaderStageFlagBits::eVertex);
shaderStages[1] = loadShader(filepath + "reflect.frag.spv",
vk::ShaderStageFlagBits::eFragment);
// Enable depth test and write
depthStencilState.depthWriteEnable = VK_TRUE;
depthStencilState.depthTestEnable = VK_TRUE;
// Flip cull mode
rasterizationState.cullMode = vk::CullModeFlagBits::eFront;
res = vkctx.device.createGraphicsPipelines(vkctx.pipelineCache, 1,
&pipelineCreateInfo, nullptr,
&pipelines.reflect);
if (res != vk::Result::eSuccess) {
throw bad_vulkan_alloc((int)res, "createGraphicsPipelines");
}
}
// Prepare and initialize uniform buffer containing shader uniforms
void
TextureCubemap::prepareUniformBuffers()
{
// 3D objact
vkctx.createBuffer(
vk::BufferUsageFlagBits::eUniformBuffer,
vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent,
sizeof(ubo),
nullptr,
&uniformData.object.buffer,
&uniformData.object.memory,
&uniformData.object.descriptor);
// Skybox
vkctx.createBuffer(
vk::BufferUsageFlagBits::eUniformBuffer,
vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent,
sizeof(ubo),
nullptr,
&uniformData.skybox.buffer,
&uniformData.skybox.memory,
&uniformData.skybox.descriptor);
updateUniformBuffers();
}
void
TextureCubemap::updateUniformBuffers()
{
// 3D object
glm::mat4 viewMatrix = glm::mat4();
ubo.projection = glm::perspective(glm::radians(60.0f),
(float)w_width / (float)w_height,
0.001f, 256.0f);
viewMatrix = glm::translate(viewMatrix, glm::vec3(0.0f, 0.0f, zoom));
// Transform the z axis to what the viewer is perceiving as the z axis to make
// the behaviour of 2-finger rotation (the value in rotation.z) understandable.
glm::mat4 zAxisRotMatrix;
zAxisRotMatrix = glm::rotate(zAxisRotMatrix, glm::radians(-rotation.x),
glm::vec3(1.0f, 0.0f, 0.0f));
zAxisRotMatrix = glm::rotate(zAxisRotMatrix, glm::radians(-rotation.y),
glm::vec3(0.0f, 1.0f, 0.0f));
zRotationAxis = normalize(zAxisRotMatrix * glm::vec4(0.0f, 0.0f, 1.0f, 1.0f));
ubo.modelView = glm::mat4();
ubo.modelView = viewMatrix * glm::translate(ubo.modelView, cameraPos);
ubo.modelView = glm::rotate(ubo.modelView, glm::radians(rotation.x),
glm::vec3(1.0f, 0.0f, 0.0f));
ubo.modelView = glm::rotate(ubo.modelView, glm::radians(rotation.y),
glm::vec3(0.0f, 1.0f, 0.0f));
ubo.modelView = glm::rotate(ubo.modelView, glm::radians(rotation.z),
zRotationAxis);
// Do the inverse here because doing it in every fragment is a bit much.
// Also MetalSL does not have inverse() and does not support passing
// transforms between stages.
ubo.invModelView = glm::inverse(ubo.modelView);
uint8_t *pData;
VK_CHECK_RESULT(vkMapMemory(vkctx.device, uniformData.object.memory, 0, sizeof(ubo), 0, (void **)&pData));
memcpy(pData, &ubo, sizeof(ubo));
vkUnmapMemory(vkctx.device, uniformData.object.memory);
// Skybox
// Remove translation from modelView so the skybox doesn't move.
ubo.modelView = glm::mat4(glm::mat3(ubo.modelView));
// Inverse not needed by skybox.
VK_CHECK_RESULT(vkMapMemory(vkctx.device, uniformData.skybox.memory, 0, sizeof(ubo), 0, (void **)&pData));
memcpy(pData, &ubo, sizeof(ubo));
vkUnmapMemory(vkctx.device, uniformData.skybox.memory);
}
void
TextureCubemap::prepareSamplerAndView()
{
// Create sampler.
vk::SamplerCreateInfo samplerInfo;
// Set the non-default values
samplerInfo.magFilter = vk::Filter::eLinear;
samplerInfo.minFilter = vk::Filter::eLinear;
samplerInfo.mipmapMode = vk::SamplerMipmapMode::eLinear;
samplerInfo.maxLod = (float)cubeMap.levelCount;
if (vkctx.gpuFeatures.samplerAnisotropy == VK_TRUE) {
samplerInfo.anisotropyEnable = VK_TRUE;
samplerInfo.maxAnisotropy = 8;
} else {
// vulkan.hpp needs fixing
samplerInfo.maxAnisotropy = 1.0;
}
samplerInfo.borderColor = vk::BorderColor::eFloatOpaqueWhite;
sampler = vkctx.device.createSampler(samplerInfo);
// Create image view.
// Textures are not directly accessed by the shaders and are abstracted
// by image views containing additional information and sub resource
// ranges.
vk::ImageViewCreateInfo viewInfo;
// Set the non-default values.
viewInfo.image = cubeMap.image;
viewInfo.format = static_cast<vk::Format>(cubeMap.imageFormat);
viewInfo.viewType = static_cast<vk::ImageViewType>(cubeMap.viewType);
viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
viewInfo.subresourceRange.layerCount = cubeMap.layerCount;
viewInfo.subresourceRange.levelCount = cubeMap.levelCount;
imageView = vkctx.device.createImageView(viewInfo);
}
void
TextureCubemap::prepare()
{
prepareSamplerAndView();
loadMeshes();
setupVertexDescriptions();
prepareUniformBuffers();
setupDescriptorSetLayout();
preparePipelines();
setupDescriptorPool();
setupDescriptorSets();
vkctx.createDrawCommandBuffers();
buildCommandBuffers();
}
void
TextureCubemap::toggleSkyBox()
{
displaySkybox = !displaySkybox;
rebuildCommandBuffers();
}
void
TextureCubemap::toggleObject(int direction)
{
meshes.objectIndex += direction;
if (meshes.objectIndex >= static_cast<int32_t>(meshes.objects.size())) {
meshes.objectIndex = 0;
} else if (meshes.objectIndex < 0) {
meshes.objectIndex = static_cast<int32_t>(meshes.objects.size()) - 1;
}
rebuildCommandBuffers();
}
void
TextureCubemap::changeLodBias(float delta)
{
ubo.lodBias += delta;
if (ubo.lodBias < 0.0f)
{
ubo.lodBias = 0.0f;
}
if (ubo.lodBias > cubeMap.levelCount)
{
ubo.lodBias = (float)cubeMap.levelCount;
}
updateUniformBuffers();
//updateTextOverlay();
}
void
TextureCubemap::keyPressed(uint32_t keyCode)
{
switch (keyCode)
{
case 's':
toggleSkyBox();
break;
case ' ':
toggleObject(+1);
break;
case SDLK_KP_PLUS:
changeLodBias(0.1f);
break;
case SDLK_KP_MINUS:
changeLodBias(-0.1f);
break;
}
}
void
TextureCubemap::getOverlayText(VulkanTextOverlay *textOverlay, float yOffset)
{
std::stringstream ss;
ss << std::setprecision(2) << std::fixed << ubo.lodBias;
textOverlay->addText("Press \"s\" to toggle skybox", 5.0f,
yOffset, VulkanTextOverlay::alignLeft);
textOverlay->addText("Press \"space\" or 2-finger swipe up or down to change object", 5.0f,
yOffset+20.0f, VulkanTextOverlay::alignLeft);
textOverlay->addText("LOD bias: " + ss.str() + " (numpad +/- to change)",
5.0f, yOffset+40.0f, VulkanTextOverlay::alignLeft);
}
const char*
TextureCubemap::customizeTitle(const char* const baseTitle)
{
if (transcoded) {
this->title = baseTitle;
this->title += " Transcoded to ";
this->title += vkFormatString((VkFormat)transcodedFormat);
return this->title.c_str();
}
return baseTitle;
}

Some files were not shown because too many files have changed in this diff Show More