mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-07 21:23:32 +00:00

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
570 lines
18 KiB
C++
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;
|
|
}
|
|
|
|
};
|