Files
renderdoc/util/test/demos/test_common.cpp
T
baldurk 8864a1d81e Improve time listing available demos
* Remove redundant repeated calls when preparing multiple tests
2025-09-29 16:40:15 +01:00

667 lines
19 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2019-2025 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#include "test_common.h"
#include <stdarg.h>
#include <stdio.h>
#include <algorithm>
const DefaultA2V DefaultTri[3] = {
{Vec3f(-0.5f, -0.5f, 0.0f), Vec4f(0.0f, 1.0f, 0.0f, 1.0f), Vec2f(0.0f, 0.0f)},
{Vec3f(0.0f, 0.5f, 0.0f), Vec4f(0.0f, 1.0f, 0.0f, 1.0f), Vec2f(0.0f, 1.0f)},
{Vec3f(0.5f, -0.5f, 0.0f), Vec4f(0.0f, 1.0f, 0.0f, 1.0f), Vec2f(1.0f, 0.0f)},
};
/* XPM */
const char *SmileyTexture[63] = {
/* columns rows colors chars-per-pixel */
"48 48 14 1 ", " c #000017", ". c #FF0017", "X c #FF735C", "o c #00FF17", "O c #FF8B5C",
"+ c #D0B95C", "@ c #E7A25C", "# c #B9D05C", "$ c #8BFF45", "% c #A2E745", "& c #A2FF45",
"* c #A2E75C", "= c #1700FF", "- c #B9FFFF",
/* pixels */
"------------------------------------------------",
"------------------------------------------------",
"------------------------------------------------",
"------------------------------------------------",
"------------------------------------------------",
"--------------------- ---------------------",
"----------------- -----------------",
"--------------- XOOOOOOO@@ ---------------",
"------------- XXXOOOOOO@@@@@@@ -------------",
"------------ XXXOOOOOO@@@@@@@@@ ------------",
"----------- XXXXOOOOOO@@@@@@@@@@++ -----------",
"---------- XXXXOOOOOO@@@@@@@@@@++++ ----------",
"--------- XXXXOOOOOO@@@@@@@@@@++++++ ---------",
"-------- XXXXOOOOOO@@@@@@@@@++++++++# --------",
"-------- XXXOOOOOO@@@@@@@@@++++++++## --------",
"------- XXXOOOOOO@@@@@@@@@++++++++#### -------",
"------- XXOOOOO...@@@@@@@@+++++ooo###### -------",
"------ XOOOOO.....@@@@@@+++++ooooo##### ------",
"------ OOOOOO.....@@@@@++++++ooooo##### ------",
"------ OOOOOOO.....@@@@+++++++ooooo####%% ------",
"------ OOOOOO@.....@@@++++++++ooooo###%%* ------",
"----- OOOOO@@.....@@++++++++#ooooo##%%*% -----",
"----- OOOO@@@@...@@++++++++###ooo##%%*%% -----",
"----- OOO@@@@@@@@@++++++++########%%*%%% -----",
"----- O@@@@@@@@@@++++++++########%%*%%%% -----",
"----- @@@@@@@@@@++++++++########%%*%%%%% -----",
"----- @@@@@@@@@++++++++########%%*%%%%%& -----",
"------ @@@@@@@@++++++++########%%*%%%%%&& ------",
"------ @@@@==+++++++++########%%*%%==%&&& ------",
"------ @@@===+++++++########%%*%%===&&& ------",
"------ @@@+===+++++########%%*%%%==&&&$ ------",
"------- @@+++===+++#######%%%*%%%==&&&$$ -------",
"------- +++++===+#######%%%*%%%==&&&$$ -------",
"-------- +++++===######%%%%%%%===&&$$ --------",
"-------- +++++#====###%%*%%%====&&$$$ --------",
"--------- +++####===#%%*%%%====&&$$$ ---------",
"---------- +######===========&&&$$$ ----------",
"----------- #######%=======&&&&$$$ -----------",
"------------ ####%%*%%%%%&&&&$$ ------------",
"------------- ##%%*%%%%%&&&&$$ -------------",
"--------------- *%%%%%&&&& ---------------",
"----------------- -----------------",
"--------------------- ---------------------",
"------------------------------------------------",
"------------------------------------------------",
"------------------------------------------------",
"------------------------------------------------",
"------------------------------------------------"};
// since tolower is int -> int, this warns below. make a char -> char alternative
char toclower(char c)
{
return (char)tolower(c);
}
char tocupper(char c)
{
return (char)toupper(c);
}
uint16_t MakeHalf(float f)
{
bool sign = f < 0.0f;
f = sign ? -f : f;
if(f < 1e-15f)
return 0;
int exp;
f = frexpf(f, &exp);
uint32_t mantissa;
memcpy(&mantissa, &f, sizeof(mantissa));
mantissa = (mantissa & 0x007fffff) >> 13;
uint16_t ret = mantissa & 0x3ff;
ret |= ((exp + 14) << 10);
if(sign)
ret |= 0x8000;
return ret;
}
std::string strlower(const std::string &str)
{
std::string newstr(str);
std::transform(newstr.begin(), newstr.end(), newstr.begin(), toclower);
return newstr;
}
std::string strupper(const std::string &str)
{
std::string newstr(str);
std::transform(newstr.begin(), newstr.end(), newstr.begin(), tocupper);
return newstr;
}
std::string trim(const std::string &str)
{
const char *whitespace = "\t \n\r";
size_t start = str.find_first_not_of(whitespace);
size_t end = str.find_last_not_of(whitespace);
// no non-whitespace characters, return the empty string
if(start == std::string::npos)
return "";
// searching from the start found something, so searching from the end must have too.
return str.substr(start, end - start + 1);
}
static char printBuf[4096] = {};
static FILE *logFile = NULL;
#if defined(ANDROID)
#include <android/log.h>
#endif
static bool debugLogEnabled = true;
void SetDebugLogEnabled(bool enabled)
{
debugLogEnabled = enabled;
}
void DebugPrint(const char *fmt, ...)
{
if(!debugLogEnabled)
return;
va_list args;
va_start(args, fmt);
vsnprintf(printBuf, 4095, fmt, args);
va_end(args);
fputs(printBuf, stdout);
fflush(stdout);
if(logFile)
{
fputs(printBuf, logFile);
fflush(logFile);
}
#if defined(WIN32)
OutputDebugStringA(printBuf);
#endif
#if defined(ANDROID)
__android_log_print(ANDROID_LOG_DEBUG, "rd_demos", "%s", printBuf);
#endif
}
void OutputPrint(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vsnprintf(printBuf, 4095, fmt, args);
va_end(args);
fputs(printBuf, stdout);
fflush(stdout);
if(logFile)
{
fputs(printBuf, logFile);
fflush(logFile);
}
#if defined(WIN32)
OutputDebugStringA(printBuf);
#endif
#if defined(ANDROID)
__android_log_print(ANDROID_LOG_INFO, "rd_demos", "%s", printBuf);
#endif
}
void LoadXPM(const char **XPM, Texture &tex)
{
uint32_t numColors = 0;
sscanf(XPM[0], "%u %u %u %*u", &tex.width, &tex.height, &numColors);
uint32_t colors[256] = {};
for(uint32_t c = 0; c < numColors; c++)
{
char ch = XPM[c + 1][0];
uint32_t col = 0;
sscanf(XPM[c + 1] + 1, " c #%x", &col);
// need to bgr swap, set full alpha
colors[ch] = 0xff000000 | ((col & 0xff) << 16) | (col & 0xff00) | ((col & 0xff0000) >> 16);
}
tex.data.resize(tex.width * tex.height);
for(uint32_t y = 0; y < tex.height; y++)
{
const char *row = XPM[1 + numColors + y];
for(uint32_t x = 0; x < tex.width; x++)
tex.data[y * tex.width + x] = colors[row[x]];
}
}
#ifndef HAVE_SHADERC
#define HAVE_SHADERC 0
#endif
// this define toggles on/off using the linked shaderc. This can be useful for quick testing without
// having to remove the built shaderc files
#define USE_LINKED_SHADERC (1 && HAVE_SHADERC)
#if !USE_LINKED_SHADERC && defined(ANDROID)
#error "can't execute shaderc on android"
#endif
#if USE_LINKED_SHADERC
#include <shaderc/shaderc.h>
#else
typedef void *shaderc_compiler_t;
#endif
static shaderc_compiler_t shaderc = NULL;
static std::string externalCompiler;
bool InternalSpvCompiler()
{
return bool(USE_LINKED_SHADERC);
}
bool SpvCompilationSupported()
{
if(shaderc)
return true;
#if USE_LINKED_SHADERC
shaderc = shaderc_compiler_initialize();
#endif
if(shaderc)
return true;
for(std::string compiler : {"glslc", "glslangValidator"})
{
FILE *pipe = popen((compiler + EXECUTABLE_SUFFIX " --version 2>&1").c_str(), "r");
if(!pipe)
continue;
int code = pclose(pipe);
if(WEXITSTATUS(code) == 0)
{
externalCompiler = compiler;
return true;
}
}
return false;
}
std::vector<uint32_t> CompileShaderToSpv(const std::string &source_text, SPIRVTarget target,
ShaderLang lang, ShaderStage stage, const char *entry_point,
const std::map<std::string, std::string> &macros)
{
std::vector<uint32_t> ret;
if(shaderc)
{
#if USE_LINKED_SHADERC
static bool logged = false;
if(!logged)
{
logged = true;
TEST_LOG("Compiling using built-in shaderc");
}
shaderc_compile_options_t opts = shaderc_compile_options_initialize();
if(lang == ShaderLang::glsl)
shaderc_compile_options_set_source_language(opts, shaderc_source_language_glsl);
else if(lang == ShaderLang::hlsl)
shaderc_compile_options_set_source_language(opts, shaderc_source_language_hlsl);
shaderc_compile_options_set_generate_debug_info(opts);
shaderc_compile_options_set_optimization_level(opts, shaderc_optimization_level_zero);
shaderc_shader_kind shader_kind = shaderc_glsl_vertex_shader;
switch(stage)
{
case ShaderStage::vert: shader_kind = shaderc_vertex_shader; break;
case ShaderStage::frag: shader_kind = shaderc_fragment_shader; break;
case ShaderStage::tesscontrol: shader_kind = shaderc_tess_control_shader; break;
case ShaderStage::tesseval: shader_kind = shaderc_tess_evaluation_shader; break;
case ShaderStage::geom: shader_kind = shaderc_geometry_shader; break;
case ShaderStage::comp: shader_kind = shaderc_compute_shader; break;
case ShaderStage::mesh: shader_kind = shaderc_mesh_shader; break;
case ShaderStage::task: shader_kind = shaderc_task_shader; break;
}
if(target == SPIRVTarget::opengl)
shaderc_compile_options_set_target_env(opts, shaderc_target_env_opengl, 0);
else if(target == SPIRVTarget::vulkan11)
shaderc_compile_options_set_target_env(opts, shaderc_target_env_vulkan,
shaderc_env_version_vulkan_1_1);
else if(target == SPIRVTarget::vulkan12)
shaderc_compile_options_set_target_env(opts, shaderc_target_env_vulkan,
shaderc_env_version_vulkan_1_2);
for(auto it : macros)
shaderc_compile_options_add_macro_definition(opts, it.first.c_str(), it.first.length(),
it.second.c_str(), it.second.length());
shaderc_compilation_result_t res;
if(lang == ShaderLang::spvasm)
res = shaderc_assemble_into_spv(shaderc, source_text.c_str(), source_text.size(), opts);
else
res = shaderc_compile_into_spv(shaderc, source_text.c_str(), source_text.size(), shader_kind,
"inshader", entry_point, opts);
shaderc_compilation_status status = shaderc_result_get_compilation_status(res);
if(status != shaderc_compilation_status_success)
{
TEST_ERROR("Couldn't compile shader with built-in shaderc: %s",
shaderc_result_get_error_message(res));
if(res)
shaderc_result_release(res);
return ret;
}
size_t sz = shaderc_result_get_length(res);
TEST_ASSERT((sz % 4) == 0, "shaderc result isn't 4-byte aligned");
ret.resize(sz / 4);
memcpy(&ret[0], shaderc_result_get_bytes(res), sz);
shaderc_result_release(res);
shaderc_compile_options_release(opts);
return ret;
#endif
}
std::string command_line;
std::string path = GetExecutableName();
path.erase(path.find_last_of("/\\"));
path += "/tmp";
MakeDir(path.c_str());
std::string infile = path + "/input";
std::string outfile = path + "/output";
if(externalCompiler == "glslc")
{
command_line = "glslc" EXECUTABLE_SUFFIX " -g -O0";
command_line += " -fentry-point=";
command_line += entry_point;
if(lang == ShaderLang::glsl)
command_line += " -x glsl";
else if(lang == ShaderLang::hlsl)
command_line += " -x hlsl";
for(auto it : macros)
command_line += " -D" + it.first + "=" + it.second;
// can't compile SPIR-V assembly if we specify a shader stage
if(lang != ShaderLang::spvasm)
{
switch(stage)
{
case ShaderStage::vert: command_line += " -fshader-stage=vert"; break;
case ShaderStage::frag: command_line += " -fshader-stage=frag"; break;
case ShaderStage::tesscontrol: command_line += " -fshader-stage=tesscontrol"; break;
case ShaderStage::tesseval: command_line += " -fshader-stage=tesseval"; break;
case ShaderStage::geom: command_line += " -fshader-stage=geom"; break;
case ShaderStage::comp: command_line += " -fshader-stage=comp"; break;
case ShaderStage::mesh: command_line += " -fshader-stage=mesh"; break;
case ShaderStage::task: command_line += " -fshader-stage=task"; break;
}
}
else
{
infile += ".spvasm";
}
if(target == SPIRVTarget::opengl)
command_line += " --target-env=opengl";
else if(target == SPIRVTarget::vulkan11)
command_line += " --target-env=vulkan1.1";
else if(target == SPIRVTarget::vulkan12)
command_line += " --target-env=vulkan1.2";
command_line += " -o ";
command_line += outfile;
command_line += " ";
command_line += infile;
}
else if(externalCompiler == "glslangValidator")
{
command_line = "glslangValidator" EXECUTABLE_SUFFIX " -g ";
command_line += " --entry-point ";
command_line += entry_point;
if(lang == ShaderLang::hlsl)
command_line += " -D";
if(lang == ShaderLang::spvasm)
{
TEST_ERROR("Can't compile SPIR-V assembly with glslangValidator");
return ret;
}
for(auto it : macros)
command_line += " -D" + it.first + "=" + it.second;
switch(stage)
{
case ShaderStage::vert: command_line += " -S vert"; break;
case ShaderStage::frag: command_line += " -S frag"; break;
case ShaderStage::tesscontrol: command_line += " -S tesscontrol"; break;
case ShaderStage::tesseval: command_line += " -S tesseval"; break;
case ShaderStage::geom: command_line += " -S geom"; break;
case ShaderStage::comp: command_line += " -S comp"; break;
case ShaderStage::mesh: command_line += " -S mesh"; break;
case ShaderStage::task: command_line += " -S task"; break;
}
if(target == SPIRVTarget::opengl)
command_line += " -G --target-env opengl";
else if(target == SPIRVTarget::vulkan11)
command_line += " -V --target-env vulkan1.1";
else if(target == SPIRVTarget::vulkan12)
command_line += " -V --target-env vulkan1.2";
else if(target == SPIRVTarget::vulkan)
command_line += " -V --target-env vulkan1.0";
command_line += " -o ";
command_line += outfile;
command_line += " ";
command_line += infile;
}
FILE *f = fopen(infile.c_str(), "wb");
if(f)
{
fwrite(source_text.c_str(), 1, source_text.size(), f);
fclose(f);
}
FILE *pipe = popen(command_line.c_str(), "r");
if(!pipe)
{
TEST_ERROR("Couldn't run %s to compile shaders.", externalCompiler.c_str());
return ret;
}
pclose(pipe);
f = fopen(outfile.c_str(), "rb");
if(f)
{
fseek(f, 0, SEEK_END);
ret.resize(ftell(f) / sizeof(uint32_t));
fseek(f, 0, SEEK_SET);
fread(&ret[0], sizeof(uint32_t), ret.size(), f);
fclose(f);
}
unlink(infile.c_str());
unlink(outfile.c_str());
return ret;
}
int GraphicsTest::maxFrameCount = -1;
std::string GraphicsTest::dataRoot;
int GraphicsTest::screenWidth = 400;
int GraphicsTest::screenHeight = 300;
bool GraphicsTest::debugDevice = false;
void GraphicsTest::Prepare(int argc, char **argv)
{
static bool prepared = false;
// nothing to do per-test if we've already prepared
if(prepared)
return;
#if USE_LINKED_SHADERC
TEST_LOG("Using linked shaderc");
#else
TEST_LOG("Requires glslc/shaderc for Vulkan tests");
#endif
prepared = true;
dataRoot = GetEnvVar("RENDERDOC_DEMOS_DATA");
if(dataRoot.empty())
dataRoot = GetCWD() + "/data/demos/";
// parse parameters
for(int i = 0; i < argc; i++)
{
if(!strcmp(argv[i], "--debug") || !strcmp(argv[i], "--validate"))
{
debugDevice = true;
}
if(i + 1 < argc && (!strcmp(argv[i], "--frames") || !strcmp(argv[i], "--framecount") ||
!strcmp(argv[i], "--max-frames")))
{
maxFrameCount = atoi(argv[i + 1]);
}
if(i + 1 < argc && !strcmp(argv[i], "--log"))
{
logFile = fopen(argv[i + 1], "w");
}
if(i + 1 < argc && (!strcmp(argv[i], "--width") || !strcmp(argv[i], "-w")))
{
screenWidth = atoi(argv[i + 1]);
if(screenWidth < 1)
screenWidth = 1;
if(screenWidth > 7680)
screenWidth = 7680;
}
if(i + 1 < argc && (!strcmp(argv[i], "--height") || !strcmp(argv[i], "-h")))
{
screenHeight = atoi(argv[i + 1]);
if(screenHeight < 1)
screenHeight = 1;
if(screenHeight > 4320)
screenHeight = 4320;
}
if(i + 1 < argc && !strcmp(argv[i], "--data"))
{
dataRoot = argv[i + 1];
while(dataRoot.back() == '/' || dataRoot.back() == '\\')
dataRoot.pop_back();
dataRoot += "/";
}
}
}
bool GraphicsTest::Init()
{
srand(0U);
pRENDERDOC_GetAPI RENDERDOC_GetAPI = NULL;
#if defined(WIN32)
HMODULE mod = GetModuleHandleA("renderdoc.dll");
if(mod)
RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)GetProcAddress(mod, "RENDERDOC_GetAPI");
#elif defined(ANDROID)
void *mod = dlopen("libVkLayer_GLES_RenderDoc.so", RTLD_NOW | RTLD_NOLOAD);
if(mod)
RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)dlsym(mod, "RENDERDOC_GetAPI");
#elif defined(__linux__)
void *mod = dlopen("librenderdoc.so", RTLD_NOW | RTLD_NOLOAD);
if(mod)
RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)dlsym(mod, "RENDERDOC_GetAPI");
#elif defined(__APPLE__)
void *mod = dlopen("librenderdoc.dylib", RTLD_NOW | RTLD_NOLOAD);
if(mod)
RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)dlsym(mod, "RENDERDOC_GetAPI");
#else
#error UNKNOWN PLATFORM
#endif
if(RENDERDOC_GetAPI)
{
int ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_0_0, (void **)&rdoc);
if(ret != 1)
rdoc = NULL;
}
return true;
}
void GraphicsTest::Shutdown()
{
}
std::string GraphicsTest::GetDataPath(const std::string &filename)
{
return dataRoot + filename;
}
bool GraphicsTest::FrameLimit()
{
curFrame++;
if(maxFrameCount > 0 && curFrame >= maxFrameCount)
return false;
return true;
}