Add ktx
This commit is contained in:
@@ -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()
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
// }
|
||||
|
||||
@@ -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 = ¤tBuffer;
|
||||
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 = ¤tBuffer;
|
||||
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.
|
||||
+1709
File diff suppressed because it is too large
Load Diff
@@ -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, ©Cmd));
|
||||
|
||||
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 = ©Cmd;
|
||||
|
||||
VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
|
||||
VK_CHECK_RESULT(vkQueueWaitIdle(queue));
|
||||
|
||||
vkFreeCommandBuffers(device, commandPool, 1, ©Cmd);
|
||||
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:
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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()
|
||||
@@ -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))
|
||||
@@ -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)},
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
+57
@@ -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"
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
@@ -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>
|
||||
Binary file not shown.
@@ -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, ¶ms);
|
||||
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;
|
||||
}
|
||||
|
||||
};
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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_ */
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user