thorvg/examples/Example.h
RuiwenTang ec26784266 gl_engine: dynamic load gl functions
no any 3rd-party libs, such as glfw or glad did not used
support windows, linux, macos and emsdk
only the actually used set of functions are loaded

Co-Authored-By: Sergii Liebodkin <sergii@lottiefiles.com>
Co-Authored-BY: Hermet Park <hermet@lottiefiles.com>

issue: https://github.com/thorvg/thorvg/issues/2453
2025-05-08 14:58:13 +09:00

570 lines
18 KiB
C++

/*
* Copyright (c) 2024 - 2025 the ThorVG project. All rights reserved.
* 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 "config.h"
#include <memory>
#include <cmath>
#include <vector>
#include <fstream>
#include <iostream>
#include <cstring>
#include <thorvg.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_syswm.h>
#ifdef _WIN32
#include <windows.h>
#ifndef PATH_MAX
#define PATH_MAX MAX_PATH
#endif
#else
#include <dirent.h>
#include <unistd.h>
#include <limits.h>
#include <sys/stat.h>
#endif
#ifdef THORVG_WG_RASTER_SUPPORT
#include <webgpu/webgpu.h>
#if defined(SDL_VIDEO_DRIVER_COCOA)
#include <Cocoa/Cocoa.h>
#include <QuartzCore/CAMetalLayer.h>
#endif
#endif
using namespace std;
/************************************************************************/
/* Common Template Code */
/************************************************************************/
namespace tvgexam
{
bool verify(tvg::Result result, string failMsg = "");
struct Example
{
uint32_t elapsed = 0;
virtual bool content(tvg::Canvas* canvas, uint32_t w, uint32_t h) = 0;
virtual bool update(tvg::Canvas* canvas, uint32_t elapsed) { return false; }
virtual bool clickdown(tvg::Canvas* canvas, int32_t x, int32_t y) { return false; }
virtual bool clickup(tvg::Canvas* canvas, int32_t x, int32_t y) { return false; }
virtual bool motion(tvg::Canvas* canvas, int32_t x, int32_t y) { return false; }
virtual void populate(const char* path) {}
virtual ~Example() {}
float timestamp()
{
return float(SDL_GetTicks()) * 0.001f;
}
void scandir(const char* path)
{
char buf[PATH_MAX];
//real path
#ifdef _WIN32
auto rpath = _fullpath(buf, path, PATH_MAX);
#else
auto rpath = realpath(path, buf);
#endif
//open directory
#ifdef _WIN32
WIN32_FIND_DATA fd;
HANDLE h = FindFirstFileEx((string(rpath) + "\\*").c_str(), FindExInfoBasic, &fd, FindExSearchNameMatch, NULL, 0);
if (h == INVALID_HANDLE_VALUE) {
cout << "Couldn't open directory \"" << rpath << "\"." << endl;
return;
}
do {
if (*fd.cFileName == '.' || *fd.cFileName == '$') continue;
if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
auto fullpath = string(path);
fullpath += '\\';
fullpath += fd.cFileName;
populate(fullpath.c_str());
}
} while (FindNextFile(h, &fd));
FindClose(h);
#else
DIR* dir = opendir(rpath);
if (!dir) {
cout << "Couldn't open directory \"" << rpath << "\"." << endl;
return;
}
//list directory
struct dirent* entry;
while ((entry = readdir(dir)) != NULL) {
if (*entry->d_name == '.' || *entry->d_name == '$') continue;
if (entry->d_type != DT_DIR) {
auto fullpath = string(path);
fullpath += '/';
fullpath += entry->d_name;
populate(fullpath.c_str());
}
}
closedir(dir);
#endif
}
};
struct Window
{
SDL_Window* window = nullptr;
tvg::Canvas* canvas = nullptr;
uint32_t width;
uint32_t height;
Example* example = nullptr;
bool needResize = false;
bool needDraw = false;
bool initialized = false;
bool clearBuffer = false;
bool print = false;
Window(Example* example, uint32_t width, uint32_t height, uint32_t threadsCnt)
{
if (!verify(tvg::Initializer::init(threadsCnt), "Failed to init ThorVG engine!")) return;
//Initialize the SDL
SDL_Init(SDL_INIT_VIDEO);
//Init member variables
this->width = width;
this->height = height;
this->example = example;
this->initialized = true;
}
virtual ~Window()
{
delete(example);
delete(canvas);
//Terminate the SDL
SDL_DestroyWindow(window);
SDL_Quit();
//Terminate ThorVG Engine
tvg::Initializer::term();
}
bool draw()
{
//Draw the contents to the Canvas
if (verify(canvas->draw(clearBuffer))) {
verify(canvas->sync());
return true;
}
return false;
}
bool ready()
{
if (!canvas) return false;
if (!example->content(canvas, width, height)) return false;
//initiate the first rendering before window pop-up.
if (!verify(canvas->draw())) return false;
if (!verify(canvas->sync())) return false;
return true;
}
void show()
{
SDL_ShowWindow(window);
refresh();
//Mainloop
SDL_Event event;
auto running = true;
auto ptime = SDL_GetTicks();
example->elapsed = 0;
uint32_t tickCnt = 0;
while (running) {
//SDL Event handling
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT: {
running = false;
break;
}
case SDL_KEYUP: {
if (event.key.keysym.sym == SDLK_ESCAPE) {
running = false;
}
break;
}
case SDL_MOUSEBUTTONDOWN: {
needDraw |= example->clickdown(canvas, event.button.x, event.button.y);
break;
}
case SDL_MOUSEBUTTONUP: {
needDraw |= example->clickup(canvas, event.button.x, event.button.y);
break;
}
case SDL_MOUSEMOTION: {
needDraw |= example->motion(canvas, event.button.x, event.button.y);
break;
}
case SDL_WINDOWEVENT: {
if (event.window.event == SDL_WINDOWEVENT_RESIZED) {
width = event.window.data1;
height = event.window.data2;
needResize = true;
needDraw = true;
}
}
}
}
if (needResize) {
resize();
needResize = false;
}
if (tickCnt > 0) {
needDraw |= example->update(canvas, example->elapsed);
}
if (needDraw) {
if (draw()) refresh();
needDraw = false;
}
auto ctime = SDL_GetTicks();
example->elapsed += (ctime - ptime);
tickCnt++;
if (print) printf("[%5d]: elapsed time = %dms (%dms)\n", tickCnt, (ctime - ptime), (example->elapsed / tickCnt));
ptime = ctime;
}
}
virtual void resize() {}
virtual void refresh() {}
};
/************************************************************************/
/* SwCanvas Window Code */
/************************************************************************/
struct SwWindow : Window
{
SwWindow(Example* example, uint32_t width, uint32_t height, uint32_t threadsCnt) : Window(example, width, height, threadsCnt)
{
if (!initialized) return;
window = SDL_CreateWindow("ThorVG Example (Software)", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE);
//Create a Canvas
canvas = tvg::SwCanvas::gen();
if (!canvas) {
cout << "SwCanvas is not supported. Did you enable the SwEngine?" << endl;
return;
}
resize();
}
void resize() override
{
auto surface = SDL_GetWindowSurface(window);
if (!surface) return;
//Set the canvas target and draw on it.
verify(static_cast<tvg::SwCanvas*>(canvas)->target((uint32_t*)surface->pixels, surface->pitch / 4, surface->w, surface->h, tvg::ColorSpace::ARGB8888));
}
void refresh() override
{
SDL_UpdateWindowSurface(window);
}
};
/************************************************************************/
/* GlCanvas Window Code */
/************************************************************************/
struct GlWindow : Window
{
SDL_GLContext context;
GlWindow(Example* example, uint32_t width, uint32_t height, uint32_t threadsCnt) : Window(example, width, height, threadsCnt)
{
if (!initialized) return;
#ifdef THORVG_GL_TARGET_GLES
SDL_SetHint(SDL_HINT_OPENGL_ES_DRIVER, "1");
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
#else
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
#endif
window = SDL_CreateWindow("ThorVG Example (OpenGL/ES)", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE);
context = SDL_GL_CreateContext(window);
//Create a Canvas
canvas = tvg::GlCanvas::gen();
if (!canvas) {
cout << "GlCanvas is not supported. Did you enable the GlEngine?" << endl;
return;
}
resize();
}
virtual ~GlWindow()
{
//Free in the reverse order of their creation.
delete(canvas);
canvas = nullptr;
SDL_GL_DeleteContext(context);
}
void resize() override
{
//Set the canvas target and draw on it.
verify(static_cast<tvg::GlCanvas*>(canvas)->target(context, 0, width, height, tvg::ColorSpace::ABGR8888S));
}
void refresh() override
{
SDL_GL_SwapWindow(window);
}
};
/************************************************************************/
/* WgCanvas Window Code */
/************************************************************************/
#ifdef THORVG_WG_RASTER_SUPPORT
struct WgWindow : Window
{
WGPUInstance instance;
WGPUSurface surface;
WGPUAdapter adapter;
WGPUDevice device;
WgWindow(Example* example, uint32_t width, uint32_t height, uint32_t threadsCnt) : Window(example, width, height, threadsCnt)
{
if (!initialized) return;
window = SDL_CreateWindow("ThorVG Example (WebGPU)", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, SDL_WINDOW_HIDDEN);
//Here we create our WebGPU surface from the window!
SDL_SysWMinfo windowWMInfo;
SDL_VERSION(&windowWMInfo.version);
SDL_GetWindowWMInfo(window, &windowWMInfo);
//Init WebGPU
WGPUInstanceDescriptor desc = {.nextInChain = nullptr};
instance = wgpuCreateInstance(&desc);
#if defined(SDL_VIDEO_DRIVER_COCOA)
[windowWMInfo.info.cocoa.window.contentView setWantsLayer:YES];
auto layer = [CAMetalLayer layer];
[windowWMInfo.info.cocoa.window.contentView setLayer:layer];
WGPUSurfaceDescriptorFromMetalLayer surfaceNativeDesc = {
.chain = {nullptr, WGPUSType_SurfaceDescriptorFromMetalLayer},
.layer = layer
};
#elif defined(SDL_VIDEO_DRIVER_X11)
WGPUSurfaceDescriptorFromXlibWindow surfaceNativeDesc = {
.chain = {nullptr, WGPUSType_SurfaceDescriptorFromXlibWindow},
.display = windowWMInfo.info.x11.display,
.window = windowWMInfo.info.x11.window
};
#elif defined(SDL_VIDEO_DRIVER_WAYLAND)
WGPUSurfaceDescriptorFromWaylandSurface surfaceNativeDesc = {
.chain = {nullptr, WGPUSType_SurfaceDescriptorFromWaylandSurface},
.display = windowWMInfo.info.wl.display,
.surface = windowWMInfo.info.wl.surface
};
#elif defined(SDL_VIDEO_DRIVER_WINDOWS)
WGPUSurfaceDescriptorFromWindowsHWND surfaceNativeDesc = {
.chain = {nullptr, WGPUSType_SurfaceDescriptorFromWindowsHWND},
.hinstance = GetModuleHandle(nullptr),
.hwnd = windowWMInfo.info.win.window
};
#endif
// create surface
WGPUSurfaceDescriptor surfaceDesc{};
surfaceDesc.nextInChain = (const WGPUChainedStruct*)&surfaceNativeDesc;
surfaceDesc.label = "The surface";
surface = wgpuInstanceCreateSurface(instance, &surfaceDesc);
// request adapter
const WGPURequestAdapterOptions requestAdapterOptions { .compatibleSurface = surface, .powerPreference = WGPUPowerPreference_HighPerformance };
auto onAdapterRequestEnded = [](WGPURequestAdapterStatus status, WGPUAdapter adapter, char const * message, void * pUserData) { *((WGPUAdapter*)pUserData) = adapter; };
wgpuInstanceRequestAdapter(instance, &requestAdapterOptions, onAdapterRequestEnded, &adapter);
// get adapter and surface properties
WGPUFeatureName featureNames[32]{};
size_t featuresCount = wgpuAdapterEnumerateFeatures(this->adapter, featureNames);
// request device
const WGPUDeviceDescriptor deviceDesc { .label = "The owned device", .requiredFeatureCount = featuresCount, .requiredFeatures = featureNames };
auto onDeviceRequestEnded = [](WGPURequestDeviceStatus status, WGPUDevice device, char const * message, void * pUserData) { *((WGPUDevice*)pUserData) = device; };
wgpuAdapterRequestDevice(this->adapter, &deviceDesc, onDeviceRequestEnded, &device);
//Create a Canvas
canvas = tvg::WgCanvas::gen();
if (!canvas) {
cout << "WgCanvas is not supported. Did you enable the WgEngine?" << endl;
return;
}
resize();
}
virtual ~WgWindow()
{
//Free in the reverse order of their creation.
delete(canvas);
canvas = nullptr;
wgpuDeviceRelease(device);
wgpuAdapterRelease(adapter);
wgpuSurfaceRelease(surface);
wgpuInstanceRelease(instance);
}
void resize() override
{
//Set the canvas target and draw on it.
verify(static_cast<tvg::WgCanvas*>(canvas)->target(device, instance, surface, width, height, tvg::ColorSpace::ABGR8888S));
}
void refresh() override
{
wgpuSurfacePresent(surface);
}
};
#else
struct WgWindow : Window
{
WgWindow(Example* example, uint32_t width, uint32_t height, uint32_t threadsCnt) : Window(example, width, height, threadsCnt)
{
cout << "webgpu driver is not detected!" << endl;
}
};
#endif
float progress(uint32_t elapsed, float durationInSec, bool rewind = false)
{
auto duration = uint32_t(durationInSec * 1000.0f); //sec -> millisec.
if (duration == 0) return 0.0f;
auto forward = ((elapsed / duration) % 2 == 0) ? true : false;
auto wrapped = elapsed % duration;
auto progress = ((float)wrapped / (float)duration);
if (rewind) return forward ? progress : (1 - progress);
return progress;
}
bool verify(tvg::Result result, string failMsg)
{
switch (result) {
case tvg::Result::FailedAllocation: {
cout << "FailedAllocation! " << failMsg << endl;
return false;
}
case tvg::Result::InsufficientCondition: {
cout << "InsufficientCondition! " << failMsg << endl;
return false;
}
case tvg::Result::InvalidArguments: {
cout << "InvalidArguments! " << failMsg << endl;
return false;
}
case tvg::Result::MemoryCorruption: {
cout << "MemoryCorruption! " << failMsg << endl;
return false;
}
case tvg::Result::NonSupport: {
cout << "NonSupport! " << failMsg << endl;
return false;
}
case tvg::Result::Unknown: {
cout << "Unknown! " << failMsg << endl;
return false;
}
default: break;
};
return true;
}
int main(Example* example, int argc, char **argv, bool clearBuffer = false, uint32_t width = 800, uint32_t height = 800, uint32_t threadsCnt = 4, bool print = false)
{
auto engine = 0; //0: sw, 1: gl, 2: wg
if (argc > 1) {
if (!strcmp(argv[1], "gl")) engine = 1;
if (!strcmp(argv[1], "wg")) engine = 2;
}
unique_ptr<Window> window;
if (engine == 0) {
window = unique_ptr<Window>(new SwWindow(example, width, height, threadsCnt));
} else if (engine == 1) {
window = unique_ptr<Window>(new GlWindow(example, width, height, threadsCnt));
} else if (engine == 2) {
window = unique_ptr<Window>(new WgWindow(example, width, height, threadsCnt));
}
window->clearBuffer = clearBuffer;
window->print = print;
if (window->ready()) {
window->show();
}
return 0;
}
};