├── .gitignore ├── examples ├── build │ ├── genie.lua │ ├── Makefile │ └── msvc_build.bat ├── px_sched_example1.cpp ├── px_sched_example3.cpp ├── common │ ├── mem_check.h │ └── render_common.h ├── px_sched_example2.cpp ├── px_sched_example8.cpp ├── px_sched_example4.cpp ├── px_sched_example5.cpp ├── px_sched_example6.cpp ├── px_render_example_imgui.cpp ├── px_sched_example7.cpp ├── px_render_example_gltf.cpp ├── px_render_example_triangle.cpp ├── deps │ ├── imconfig.h │ ├── sokol_time.h │ └── imstb_rectpack.h └── px_render_example_rtt.cpp ├── README.md ├── LICENSE ├── README_px_render.md ├── README_px_sched.md ├── px_render_imgui.h ├── px_mem.h ├── px_render_gltf.h └── px_sched.h /.gitignore: -------------------------------------------------------------------------------- 1 | examples/* 2 | -------------------------------------------------------------------------------- /examples/build/genie.lua: -------------------------------------------------------------------------------- 1 | solution "px" 2 | configurations { "Debug", "Release" } 3 | platforms {"x32", "x64"} 4 | language "C++" 5 | includedirs {"..","deps"} 6 | location "build" 7 | objdir "build/obj" 8 | 9 | exampleList = { 10 | --"px_render_example_gltf.cpp", 11 | --"px_render_example_imgui.cpp", 12 | --"px_render_example_rtt.cpp", 13 | --"px_render_example_triangle.cpp", 14 | "px_sched_example1.cpp", 15 | "px_sched_example2.cpp", 16 | "px_sched_example3.cpp", 17 | "px_sched_example4.cpp", 18 | "px_sched_example5.cpp", 19 | "px_sched_example6.cpp", 20 | "px_sched_example7.cpp", 21 | "px_sched_example8.cpp", 22 | } 23 | 24 | for _,a in ipairs(exampleList) do 25 | project(a) 26 | kind "ConsoleApp" 27 | files{"../"..a} 28 | end 29 | -------------------------------------------------------------------------------- /examples/px_sched_example1.cpp: -------------------------------------------------------------------------------- 1 | // Example-1: 2 | // launch N tasks in parallel, wait for all of them to finish 3 | 4 | #define PX_SCHED_IMPLEMENTATION 1 5 | #include "../px_sched.h" 6 | 7 | int main(int, char **) { 8 | px_sched::Scheduler schd; 9 | schd.init(); 10 | 11 | // (a) Sync objects can be used later to wait for them 12 | px_sched::Sync s; 13 | for(size_t i = 0; i < 128; ++i) { 14 | auto job = [i] { 15 | printf("Task %zu completed from %s\n", 16 | i, px_sched::Scheduler::current_thread_name()); 17 | }; 18 | // run a task, and notify to the given sync object 19 | schd.run(job, &s); 20 | } 21 | 22 | printf("Waiting for tasks to finish...\n"); 23 | schd.waitFor(s); // wait for all tasks to finish 24 | printf("Waiting for tasks to finish...DONE \n"); 25 | 26 | return 0; 27 | } 28 | -------------------------------------------------------------------------------- /examples/px_sched_example3.cpp: -------------------------------------------------------------------------------- 1 | // Example-3: 2 | // launch tasks in sequence 3 | 4 | #define PX_SCHED_IMPLEMENTATION 1 5 | #include "../px_sched.h" 6 | 7 | int main(int, char **) { 8 | px_sched::Scheduler schd; 9 | schd.init(); 10 | 11 | px_sched::Sync s; 12 | for(size_t i = 0; i < 128; ++i) { 13 | auto job = [i] { 14 | printf("Task %zu completed from %s\n", 15 | i, px_sched::Scheduler::current_thread_name()); 16 | }; 17 | px_sched::Sync new_s; 18 | // run job after s 19 | schd.runAfter(s, job, &new_s); 20 | // sync objects can be copied, store the new sync object for the next task 21 | s = new_s; 22 | } 23 | 24 | printf("Waiting for tasks to finish...\n"); 25 | schd.waitFor(s); // wait for all tasks to finish 26 | printf("Waiting for tasks to finish...DONE \n"); 27 | 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /examples/common/mem_check.h: -------------------------------------------------------------------------------- 1 | size_t GLOBAL_amount_alloc = 0; 2 | size_t GLOBAL_amount_dealloc = 0; 3 | 4 | void *mem_check_alloc(size_t alignment, size_t s) { 5 | if (alignment < sizeof(void*)) alignment = sizeof(void*); 6 | size_t *ptr = static_cast(aligned_alloc(alignment, sizeof(size_t)*32+s)); 7 | // we use a buffer of 32*size_t (4/8) to accomodate big alignments up to 128b if needed 8 | GLOBAL_amount_alloc += s; 9 | *ptr = s; 10 | return ptr+32; 11 | } 12 | 13 | void mem_check_free(void *raw_ptr) { 14 | size_t *ptr = static_cast(raw_ptr); 15 | GLOBAL_amount_dealloc += ptr[-32]; 16 | free(ptr-32); 17 | } 18 | 19 | void mem_report() { 20 | printf("Total memory allocated: %zu\n", GLOBAL_amount_alloc); 21 | printf("Total memory freed: %zu\n", GLOBAL_amount_dealloc); 22 | if (GLOBAL_amount_alloc != GLOBAL_amount_dealloc) abort(); 23 | } 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # px 2 | 'PpluX' Single header C++14 Libraries 3 | 4 | | Name | Code | Description | 5 | |------|------|-------------| 6 | | px_sched | [px_sched.h](px_sched.h) | Task oriented scheduler. See [more](README_px_sched.md) | 7 | | px_mem | [px_mem.h](px_mem.h) | Safe memory management constructs (safer unique_ptr with futher restrictions, and avoiding new/delete completely)| 8 | 9 | ## Old libraries (not updated for a long time) 10 | 11 | These libraries are not actively being developed anymore, use them at your own risk :) 12 | 13 | | Name | Code | Description | 14 | |------|------|-------------| 15 | | px_render | [px_render.h](px_render.h) | Multithreaded, Command Based render backend. See [more](README_px_render.md) (**WIP**)| 16 | | px_render_gltf | [px_render_gltf.h](px_render_gltf.h) | Module for px_render to load GLTF (thanks to [tinygltf](https://github.com/syoyo/tinygltf)) [example](examples/px_render_example_gltf.cpp)| 17 | | px_render_imgui | [px_render_imgui.h](px_render_imgui.h) | [Dear Imgui](https://github.com/ocornut/imgui) render backend for px_render 18 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 PpluX 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/px_sched_example2.cpp: -------------------------------------------------------------------------------- 1 | // Example-2: 2 | // Custom Job Definition 3 | 4 | #include 5 | // Job definition must be done before including px_sched.h 6 | #define PX_SCHED_CUSTOM_JOB_DEFINITION 7 | namespace px_sched { 8 | struct Job { 9 | void (*func)(size_t); 10 | size_t n; 11 | void operator()() { func(n); } 12 | }; 13 | } // px namespace 14 | 15 | #define PX_SCHED_IMPLEMENTATION 1 16 | #include "../px_sched.h" 17 | 18 | void task(size_t n) { 19 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 20 | printf("Task %zu completed from %s\n", n, px_sched::Scheduler::current_thread_name()); 21 | } 22 | 23 | int main(int, char **) { 24 | px_sched::Scheduler schd; 25 | schd.init(); 26 | 27 | // (a) Sync objects can be used later to wait for them 28 | px_sched::Sync s; 29 | for(size_t i = 0; i < 128; ++i) { 30 | px_sched::Job job { task, i }; 31 | // run a task, and notify to the given sync object 32 | schd.run(std::move(job), &s); 33 | } 34 | 35 | printf("Waiting for tasks to finish...\n"); 36 | schd.waitFor(s); // wait for all tasks to finish 37 | printf("Waiting for tasks to finish...DONE \n"); 38 | 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /examples/px_sched_example8.cpp: -------------------------------------------------------------------------------- 1 | // Example-8: 2 | // Job order (mainly to check Single Threaded implementation) 3 | 4 | #define PX_SCHED_IMPLEMENTATION 1 5 | #include "../px_sched.h" 6 | #include 7 | 8 | int main(int, char **) { 9 | px_sched::Scheduler schd; 10 | schd.init(); 11 | 12 | // (a) Sync objects can be used later to wait for them 13 | px_sched::Sync start; 14 | px_sched::Sync middle; 15 | px_sched::Sync end; 16 | schd.incrementSync(&start); 17 | 18 | schd.incrementSync(&middle); 19 | size_t data[128] = {}; 20 | schd.runAfter(middle, [&data]{ 21 | printf("Checking...\n"); 22 | for(size_t i = 0; i < 128; ++i) { 23 | assert(data[i] == i*2); 24 | } 25 | }, &end); 26 | for(size_t i = 0; i < 128; ++i) { 27 | schd.runAfter(start,[i, &data] () mutable { 28 | printf("Running %zu\n",i); 29 | data[i] = i*2; 30 | }, &middle); 31 | } 32 | schd.decrementSync(&middle); 33 | assert(data[127] == 0); 34 | // start now 35 | schd.decrementSync(&start); 36 | printf("Waiting for tasks to finish...\n"); 37 | schd.waitFor(end); // wait for all tasks to finish 38 | assert(data[127] == 254); 39 | printf("Waiting for tasks to finish...DONE \n"); 40 | 41 | return 0; 42 | } 43 | -------------------------------------------------------------------------------- /examples/px_sched_example4.cpp: -------------------------------------------------------------------------------- 1 | // Example-4: 2 | // Tasks can launch sub-tasks and wait for them 3 | 4 | #define PX_SCHED_IMPLEMENTATION 1 5 | #include "../px_sched.h" 6 | #include "common/mem_check.h" 7 | 8 | int main(int, char **) { 9 | atexit(mem_report); 10 | px_sched::Scheduler schd; 11 | px_sched::SchedulerParams s_params; 12 | s_params.mem_callbacks.alloc_fn = mem_check_alloc; 13 | s_params.mem_callbacks.free_fn = mem_check_free; 14 | schd.init(s_params); 15 | 16 | px_sched::Sync s; 17 | for(size_t i = 0; i < 10; ++i) { 18 | auto job = [i] { 19 | printf("Phase 1: Task %zu completed from %s\n", 20 | i, px_sched::Scheduler::current_thread_name()); 21 | }; 22 | schd.run(job, &s); 23 | } 24 | 25 | px_sched::Sync last; 26 | schd.runAfter(s, [&schd]{ 27 | printf("Phase 2\n"); 28 | px_sched::Sync s2; 29 | for(size_t i = 0; i < 10; ++i) { 30 | auto job = [i] { 31 | printf("Phase 2: Task %zu completed from %s\n", 32 | i, px_sched::Scheduler::current_thread_name()); 33 | }; 34 | schd.run(job, &s2); 35 | } 36 | schd.waitFor(s2); 37 | printf("Phase 2, done\n"); 38 | }, &last); 39 | 40 | printf("Waiting for tasks to finish...\n"); 41 | schd.waitFor(last); // wait for all tasks to finish 42 | printf("Waiting for tasks to finish...DONE \n"); 43 | 44 | return 0; 45 | } 46 | -------------------------------------------------------------------------------- /examples/px_sched_example5.cpp: -------------------------------------------------------------------------------- 1 | // Example-5: 2 | // Launch 3 phases, without Waiting and locking 3 | 4 | #define PX_SCHED_IMPLEMENTATION 1 5 | #include "../px_sched.h" 6 | #include "common/mem_check.h" 7 | 8 | int main(int, char **) { 9 | atexit(mem_report); 10 | px_sched::Scheduler schd; 11 | px_sched::SchedulerParams s_params; 12 | s_params.mem_callbacks.alloc_fn = mem_check_alloc; 13 | s_params.mem_callbacks.free_fn = mem_check_free; 14 | schd.init(s_params); 15 | 16 | px_sched::Sync s1,s2,s3; 17 | for(size_t i = 0; i < 10; ++i) { 18 | auto job = [i] { 19 | printf("Phase 1: Task %zu completed from %s\n", 20 | i, px_sched::Scheduler::current_thread_name()); 21 | }; 22 | schd.run(job, &s1); 23 | } 24 | 25 | for(size_t i = 0; i < 10; ++i) { 26 | auto job = [i] { 27 | printf("Phase 2: Task %zu completed from %s\n", 28 | i, px_sched::Scheduler::current_thread_name()); 29 | }; 30 | schd.runAfter(s1, job, &s2); 31 | } 32 | 33 | for(size_t i = 0; i < 10; ++i) { 34 | auto job = [i] { 35 | printf("Phase 3: Task %zu completed from %s\n", 36 | i, px_sched::Scheduler::current_thread_name()); 37 | }; 38 | schd.runAfter(s2, job, &s3); 39 | } 40 | 41 | 42 | px_sched::Sync last = s3; 43 | printf("Waiting for tasks to finish...\n"); 44 | schd.waitFor(last); // wait for all tasks to finish 45 | printf("Waiting for tasks to finish...DONE \n"); 46 | 47 | return 0; 48 | } 49 | -------------------------------------------------------------------------------- /examples/px_sched_example6.cpp: -------------------------------------------------------------------------------- 1 | // Example-6: 2 | // Manually working with sync objects to control the launch of tasks 3 | 4 | #define PX_SCHED_IMPLEMENTATION 1 5 | #include "../px_sched.h" 6 | #include "common/mem_check.h" 7 | 8 | int main(int, char **) { 9 | atexit(mem_report); 10 | px_sched::Scheduler schd; 11 | px_sched::SchedulerParams s_params; 12 | s_params.mem_callbacks.alloc_fn = mem_check_alloc; 13 | s_params.mem_callbacks.free_fn = mem_check_free; 14 | schd.init(s_params); 15 | 16 | px_sched::Sync s1,s2; 17 | for(size_t i = 0; i < 10; ++i) { 18 | auto job = [i] { 19 | printf("Phase 1: Task %zu completed from %s\n", 20 | i, px_sched::Scheduler::current_thread_name()); 21 | }; 22 | schd.run(job, &s1); 23 | } 24 | 25 | // manually increment the sync object, to control any task that will be 26 | // attached to it. Tasks are executed when sync objects reach zero. 27 | schd.incrementSync(&s1); 28 | 29 | for(size_t i = 0; i < 10; ++i) { 30 | auto job = [i] { 31 | printf("Phase 2: Task %zu completed from %s\n", 32 | i, px_sched::Scheduler::current_thread_name()); 33 | }; 34 | schd.runAfter(s1, job, &s2); 35 | } 36 | 37 | printf("Holding sync object 1 to prevent launch of phase2\n"); 38 | std::this_thread::sleep_for(std::chrono::seconds(2)); 39 | printf("releasing Sync-1...\n"); 40 | schd.decrementSync(&s1); 41 | 42 | px_sched::Sync last = s2; 43 | printf("Waiting for tasks to finish...\n"); 44 | schd.waitFor(last); // wait for all tasks to finish 45 | printf("Waiting for tasks to finish...DONE \n"); 46 | 47 | 48 | return 0; 49 | } 50 | -------------------------------------------------------------------------------- /examples/build/Makefile: -------------------------------------------------------------------------------- 1 | 2 | CXXFLAGS=-std=c++11 -pedantic -g -O3\ 3 | -Wall \ 4 | -Wcast-align \ 5 | -Wcast-qual \ 6 | -Wctor-dtor-privacy \ 7 | -Wdisabled-optimization \ 8 | -Wdouble-promotion \ 9 | -Werror \ 10 | -Wextra \ 11 | -Wformat=2 \ 12 | -Winit-self \ 13 | -Wmissing-include-dirs \ 14 | -Wno-unused\ 15 | -Wold-style-cast \ 16 | -Woverloaded-virtual \ 17 | -Wredundant-decls \ 18 | -Wshadow \ 19 | -Wsign-conversion \ 20 | -Wsign-promo \ 21 | -Wstrict-overflow=5 \ 22 | -Wswitch-default \ 23 | -Wundef \ 24 | -fsanitize=address \ 25 | 26 | UNAME_S := $(shell uname -s) 27 | 28 | ifeq ($(UNAME_S),Linux) 29 | LDFLAGS += -lpthread 30 | endif 31 | 32 | px_sched_examples = px_sched_example1 px_sched_example2 px_sched_example3 px_sched_example4 px_sched_example5 px_sched_example6 px_sched_example7 px_sched_example8 33 | px_render_examples = px_render_example_imgui #WIP: px_render_example_rtt px_render_example_triangle 34 | 35 | all: $(px_sched_examples) 36 | 37 | $(px_sched_examples): %: ../%.cpp ../../px_sched.h 38 | $(CXX) $(CXXFLAGS) -o $@ $< $(LDFLAGS) 39 | $(CXX) -DPX_SCHED_CONFIG_SINGLE_THREAD $(CXXFLAGS) -o $@_noMT $< $(LDFLAGS) 40 | 41 | 42 | $(px_render_examples): %: ../%.cpp 43 | $(CXX) -std=c++14 -fpermissive -D linux -g -O2 -I .. -o $@ $< $(LDFLAGS) -ldl -lX11 -lXi -lXcursor 44 | 45 | .PHONY: clean tests 46 | clean: 47 | rm -f $(px_sched_examples) 48 | 49 | tests: $(px_sched_examples) 50 | ./px_sched_example1 51 | ./px_sched_example2 52 | ./px_sched_example3 53 | ./px_sched_example4 54 | ./px_sched_example5 55 | ./px_sched_example6 56 | ./px_sched_example7 57 | ./px_sched_example8 58 | @echo "ALL px_sched_examples executed" 59 | ./px_sched_example1_noMT 60 | ./px_sched_example2_noMT 61 | ./px_sched_example3_noMT 62 | ./px_sched_example4_noMT 63 | ./px_sched_example5_noMT 64 | ./px_sched_example6_noMT 65 | ./px_sched_example7_noMT 66 | ./px_sched_example8_noMT 67 | @echo "ALL px_sched_examples executed (no MT)" 68 | -------------------------------------------------------------------------------- /examples/build/msvc_build.bat: -------------------------------------------------------------------------------- 1 | REM Copyright 2018-2020 (C) PpluX (Jose L. Hidalgo) 2 | REM To Compile the examples under MSVC (2019 Community) 3 | @ECHO OFF 4 | SET INPUT=%1 5 | SET OUTPUT_D=%INPUT:.cpp=_debug.exe% 6 | SET OUTPUT_R=%INPUT:.cpp=_release.exe% 7 | SET ARCH=x64 8 | 9 | call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\Tools\vsdevcmd" -arch=%ARCH% 10 | :loop 11 | echo ---------------------------------------------------------------- 12 | echo %INPUT% (%ARCH%) 13 | echo ---------------------------------------------------------------- 14 | echo Choose Option: 15 | echo 1: Compile (%OUTPUT_D%) 16 | echo 2: Execute (%OUTPUT_D%) 17 | echo 3: Launch DevEnv 18 | echo 4: Compile (%OUTPUT_R%) 19 | echo 5: Execute (%OUTPUT_R%) 20 | echo 6: Exit 21 | CHOICE /C:123456 /M "Option" 22 | goto case_%ERRORLEVEL% 23 | :case_1 24 | cls 25 | echo ---------------------------------------------------------------- 26 | echo -- BUILD -- 27 | echo ---------------------------------------------------------------- 28 | cl /nologo /Zi /GR /EHs /MDd -DWIN32 %INPUT% opengl32.lib user32.lib gdi32.lib shell32.lib /link /OUT:%OUTPUT_D% 29 | goto loop 30 | :case_2 31 | cls 32 | echo ---------------------------------------------------------------- 33 | echo -- EXECUTE -- 34 | echo ---------------------------------------------------------------- 35 | %OUTPUT_D% 36 | goto loop 37 | :case_3 38 | echo ---------------------------------------------------------------- 39 | echo -- DEV ENV -- 40 | echo ---------------------------------------------------------------- 41 | devenv %OUTPUT_D% %INPUT% 42 | goto loop 43 | :case_4 44 | cls 45 | echo ---------------------------------------------------------------- 46 | echo -- BUILD -RELEASE- -- 47 | echo ---------------------------------------------------------------- 48 | cl /nologo /GR /EHs /MD -DWIN32 %INPUT% opengl32.lib user32.lib gdi32.lib shell32.lib /link /OUT:%OUTPUT_R% 49 | goto loop 50 | :case_5 51 | cls 52 | echo ---------------------------------------------------------------- 53 | echo -- EXECUTE -RELEASE- -- 54 | echo ---------------------------------------------------------------- 55 | %OUTPUT_R% 56 | goto loop 57 | :case_6 58 | -------------------------------------------------------------------------------- /examples/px_render_example_imgui.cpp: -------------------------------------------------------------------------------- 1 | #include "common/render_common.h" 2 | 3 | // demo 4 | #include "deps/imgui_demo.cpp" 5 | 6 | using namespace px_render; 7 | 8 | struct { 9 | Pipeline material; 10 | Buffer vertex_buff; 11 | Buffer index_buff; 12 | } State ; 13 | 14 | float vertex_data[] = { 15 | -1.0, -1.0, 0.0, 1.0, 0.0, 0.0, 16 | 1.0, -1.0, 0.0, 0.0, 1.0, 0.0, 17 | 0.0, 0.8, 0.0, 0.0, 0.0, 1.0, 18 | }; 19 | 20 | uint16_t index_data[] = { 21 | 0, 1, 2, 22 | }; 23 | 24 | void init(px_render::RenderContext *ctx, px_sched::Scheduler *sched) { 25 | 26 | freopen("px_log.txt", "w", stdout); 27 | 28 | Pipeline::Info pipeline_info; 29 | pipeline_info.shader.vertex = GLSL( 30 | uniform mat4 u_viewProjection; 31 | in vec3 position; 32 | in vec3 color; 33 | out vec3 v_color; 34 | void main() { 35 | gl_Position = u_viewProjection * vec4(position,1.0); 36 | v_color = color; 37 | } 38 | ); 39 | pipeline_info.shader.fragment = GLSL( 40 | in vec3 v_color; 41 | out vec4 color_out; 42 | void main() { 43 | color_out = vec4(v_color,1.0); 44 | } 45 | ); 46 | pipeline_info.attribs[0] = {"position", VertexFormat::Float3}; 47 | pipeline_info.attribs[1] = {"color", VertexFormat::Float3}; 48 | State.material = ctx->createPipeline(pipeline_info); 49 | 50 | State.vertex_buff = ctx->createBuffer({BufferType::Vertex, sizeof(vertex_data), Usage::Static}); 51 | State.index_buff = ctx->createBuffer({BufferType::Index, sizeof(index_data), Usage::Static}); 52 | // upload data 53 | DisplayList dl; 54 | dl.fillBufferCommand() 55 | .set_buffer(State.vertex_buff) 56 | .set_data(vertex_data) 57 | .set_size(sizeof(vertex_data)); 58 | dl.fillBufferCommand() 59 | .set_buffer(State.index_buff) 60 | .set_data(index_data) 61 | .set_size(sizeof(index_data)); 62 | 63 | ctx->submitDisplayList(std::move(dl)); 64 | } 65 | 66 | void finish(px_render::RenderContext *ctx, px_sched::Scheduler *sched) { 67 | } 68 | 69 | void render(px_render::RenderContext *ctx, px_sched::Scheduler *sched) { 70 | Mat4 proj; 71 | Mat4 view; 72 | 73 | gb_mat4_perspective((gbMat4*)&proj, gb_to_radians(45.f), sapp_width()/(float)sapp_height(), 0.05f, 900.0f); 74 | gb_mat4_look_at((gbMat4*)&view, {0.f,0.0f,3.f}, {0.f,0.f,0.0f}, {0.f,1.f,0.f}); 75 | 76 | px_render::DisplayList dl; 77 | dl.setupViewCommand() 78 | .set_viewport({0,0, (uint16_t) sapp_width(), (uint16_t) sapp_height()}) 79 | .set_projection_matrix(proj) 80 | .set_view_matrix(view) 81 | ; 82 | dl.clearCommand() 83 | .set_color({0.5f,0.7f,0.8f,1.0f}) 84 | .set_clear_color(true) 85 | .set_clear_depth(true) 86 | ; 87 | dl.setupPipelineCommand() 88 | .set_pipeline(State.material) 89 | .set_buffer(0,State.vertex_buff) 90 | ; 91 | dl.renderCommand() 92 | .set_index_buffer(State.index_buff) 93 | .set_count(3) 94 | .set_type(IndexFormat::UInt16) 95 | ; 96 | 97 | ImGui::NewFrame(); 98 | ImGui::ShowDemoWindow(); 99 | ImGui::Render(); 100 | ImGui_Impl_pxrender_RenderDrawData(ImGui::GetDrawData(), &dl); 101 | 102 | ctx->submitDisplayListAndSwap(std::move(dl)); 103 | } 104 | -------------------------------------------------------------------------------- /examples/px_sched_example7.cpp: -------------------------------------------------------------------------------- 1 | // Example-7: 2 | // Multiple Readers, Single Writer pattern 3 | 4 | #include // demo: rand 5 | #include // for the scoped lock 6 | 7 | //#define PX_SCHED_CONFIG_SINGLE_THREAD 1 8 | #define PX_SCHED_IMPLEMENTATION 1 9 | #include "../px_sched.h" 10 | #include "common/mem_check.h" 11 | 12 | 13 | template 14 | class MRSW { 15 | public: 16 | ~MRSW() { finish(); } 17 | 18 | void init(px_sched::Scheduler *s) { 19 | finish(); 20 | sched_ = s; 21 | void *ptr = sched_->params().mem_callbacks.alloc_fn(alignof(T), sizeof(T)); 22 | obj_ = new (ptr) T(); 23 | read_mode_ = true; 24 | } 25 | 26 | void finish() { 27 | if (obj_) { 28 | sched_->waitFor(next_); 29 | obj_->~T(); 30 | sched_->params().mem_callbacks.free_fn(obj_); 31 | obj_ = nullptr; 32 | sched_ = nullptr; 33 | } 34 | } 35 | 36 | template 37 | void executeRead(T_func func, px_sched::Sync *finish_signal = nullptr) { 38 | std::lock_guard g(lock_); 39 | if (!read_mode_) { 40 | read_mode_ = true; 41 | prev_ = next_; 42 | next_ = px_sched::Sync(); 43 | } 44 | if (finish_signal) sched_->incrementSync(finish_signal); 45 | sched_->runAfter(prev_, [this, func, finish_signal]() { 46 | func(static_cast(obj_)); 47 | if (finish_signal) sched_->decrementSync(finish_signal); 48 | }, &next_); 49 | } 50 | 51 | template 52 | void executeWrite(T_func func, px_sched::Sync *finish_signal = nullptr) { 53 | std::lock_guard g(lock_); 54 | read_mode_ = false; 55 | px_sched::Sync new_next; 56 | if (finish_signal) sched_->incrementSync(finish_signal); 57 | sched_->runAfter(next_, [this, func, finish_signal]() { 58 | func(obj_); 59 | if (finish_signal) sched_->decrementSync(finish_signal); 60 | }, &new_next); 61 | next_ = new_next; 62 | } 63 | private: 64 | px_sched::Scheduler *sched_ = nullptr; 65 | T *obj_ = nullptr; 66 | px_sched::Sync prev_; 67 | px_sched::Sync next_; 68 | px_sched::Spinlock lock_; 69 | bool read_mode_; 70 | }; 71 | 72 | struct Example { 73 | mutable std::atomic readers = {0}; 74 | std::atomic writers = {0}; 75 | }; 76 | 77 | int main(int, char **) { 78 | atexit(mem_report); 79 | px_sched::Scheduler sched_; 80 | px_sched::SchedulerParams s_params; 81 | s_params.max_number_tasks = 8196; 82 | s_params.mem_callbacks.alloc_fn = mem_check_alloc; 83 | s_params.mem_callbacks.free_fn = mem_check_free; 84 | sched_.init(s_params); 85 | 86 | MRSW example; 87 | example.init(&sched_); 88 | 89 | for(uint32_t i = 0; i < 1000; ++i) { 90 | if ((std::rand() & 0xFF) < 200) { 91 | example.executeRead([i](const Example *e) { 92 | e->readers.fetch_add(1); 93 | printf("[%u] Read Op %d(R)/%d(W)\n", i, e->readers.load(), e->writers.load()); 94 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); 95 | e->readers.fetch_sub(1); 96 | }); 97 | } else { 98 | example.executeWrite([i](Example *e) { 99 | e->writers.fetch_add(1); 100 | printf("[%u] Write Op %d(R)/%d(W)\n", i, e->readers.load(), e->writers.load()); 101 | std::this_thread::sleep_for(std::chrono::milliseconds(3)); 102 | e->writers.fetch_sub(1); 103 | }); 104 | } 105 | } 106 | 107 | printf("WAITING FOR TASKS TO FINISH....\n"); 108 | 109 | 110 | return 0; 111 | } 112 | -------------------------------------------------------------------------------- /examples/px_render_example_gltf.cpp: -------------------------------------------------------------------------------- 1 | #include "common/render_common.h" 2 | 3 | #define TINYGLTF_IMPLEMENTATION 4 | #define TINYGLTF_NO_STB_IMAGE_WRITE 5 | #define STB_IMAGE_IMPLEMENTATION 6 | #define PX_RENDER_GLTF_IMPLEMENTATION 7 | #include "deps/tiny_gltf.h" 8 | #include "../px_render_gltf.h" 9 | 10 | using namespace px_render; 11 | 12 | struct { 13 | Pipeline material; 14 | GLTF gltf; 15 | std::atomic gltf_ready = {false}; 16 | } State ; 17 | 18 | void init(px_render::RenderContext *ctx, px_sched::Scheduler *sched) { 19 | Pipeline::Info pipeline_info; 20 | pipeline_info.shader.vertex = GLSL( 21 | uniform mat4 u_viewProjection; 22 | in vec3 position; 23 | in vec3 normal; 24 | in vec2 uv; 25 | out vec2 v_tex; 26 | void main() { 27 | gl_Position = u_viewProjection * vec4(position,1.0); 28 | v_tex = uv; 29 | } 30 | ); 31 | pipeline_info.shader.fragment = GLSL( 32 | out vec4 color_out; 33 | in vec2 v_tex; 34 | uniform sampler2D u_tex0; 35 | void main() { 36 | color_out = texture(u_tex0, v_tex); 37 | } 38 | ); 39 | pipeline_info.attribs[0] = {"position", VertexFormat::Float3}; 40 | pipeline_info.attribs[1] = {"normal", VertexFormat::Float3}; 41 | pipeline_info.attribs[2] = {"uv", VertexFormat::Float2}; 42 | pipeline_info.textures[0] = TextureType::T2D; 43 | State.material = ctx->createPipeline(pipeline_info); 44 | 45 | sched->run([ctx]{ 46 | tinygltf::TinyGLTF loader; 47 | tinygltf::Model model; 48 | std::string err; 49 | std::string warning; 50 | if (!loader.LoadASCIIFromFile(&model, &err, &warning, "t2/Scene.gltf")) { 51 | fprintf(stderr,"Error loading GLTF %s", err.c_str()); 52 | assert(!"GLTF_ERROR"); 53 | } 54 | State.gltf.init(ctx, model); 55 | State.gltf_ready = true; 56 | }); 57 | } 58 | 59 | void finish(px_render::RenderContext *ctx, px_sched::Scheduler *sched) { 60 | } 61 | 62 | void render(px_render::RenderContext *ctx, px_sched::Scheduler *sched) { 63 | Mat4 proj; 64 | Mat4 view; 65 | 66 | float dmin = std::min({State.gltf.bounds_min.f[0], State.gltf.bounds_min.f[1], State.gltf.bounds_min.f[2]}); 67 | float dmax = std::max({State.gltf.bounds_max.f[0], State.gltf.bounds_max.f[1], State.gltf.bounds_max.f[2]}); 68 | 69 | gb_mat4_perspective((gbMat4*)&proj, gb_to_radians(45.f), sapp_width()/(float)sapp_height(), 1.0f, (dmax-dmin)*2.0f); 70 | gb_mat4_look_at((gbMat4*)&view, {0.f,(dmax-dmin)*1.2f,0.0f}, {0.f,0.f,0.0f}, {0.f,0.f,1.f}); 71 | static float angle = 0.0f; 72 | angle += 0.01f; 73 | view = Mat4::Mult(view, Mat4::SRT({1.0f,1.0f,1.0f}, {0.0f, 0.0f, 1.0f, angle}, {0.0f, 0.0f, 0.0f})); 74 | px_render::DisplayList dl; 75 | dl.setupViewCommand() 76 | .set_viewport({0,0, (uint16_t) sapp_width(), (uint16_t) sapp_height()}) 77 | .set_projection_matrix(proj) 78 | .set_view_matrix(view) 79 | ; 80 | dl.clearCommand() 81 | .set_color({0.5f,0.7f,0.8f,1.0f}) 82 | .set_clear_color(true) 83 | .set_clear_depth(true) 84 | ; 85 | if (State.gltf_ready) { 86 | for(uint32_t i = 0; i < State.gltf.num_primitives; ++i) { 87 | const auto &node_idx = State.gltf.primitives[i].node; 88 | const auto &model = State.gltf.nodes[node_idx].model; 89 | const auto &material = State.gltf.materials[State.gltf.primitives[i].material]; 90 | dl.setupPipelineCommand() 91 | .set_pipeline(State.material) 92 | .set_buffer(0,State.gltf.vertex_buffer) 93 | .set_model_matrix(model) 94 | .set_texture(0, State.gltf.textures[material.base_color.texture].tex); 95 | ; 96 | dl.renderCommand() 97 | .set_index_buffer(State.gltf.index_buffer) 98 | .set_count(State.gltf.primitives[i].index_count) 99 | .set_offset(State.gltf.primitives[i].index_offset*sizeof(uint32_t)) 100 | .set_type(IndexFormat::UInt32) 101 | ; 102 | } 103 | } 104 | 105 | ImGui::NewFrame(); 106 | ImGui::Render(); 107 | ImGui_Impl_pxrender_RenderDrawData(ImGui::GetDrawData(), &dl); 108 | 109 | ctx->submitDisplayListAndSwap(std::move(dl)); 110 | } 111 | -------------------------------------------------------------------------------- /examples/px_render_example_triangle.cpp: -------------------------------------------------------------------------------- 1 | // Emscripten config 2 | #ifdef __EMSCRIPTEN__ 3 | // Compile with emcc --std=c++14 -s USE_WEBGL2=1 px_render_example_rtt.cpp -o rtt.html 4 | # define SOKOL_GLES3 5 | # define PX_RENDER_BACKEND_GLES 6 | # define GLSL(...) "#version 300 es\n precision highp float;\n" #__VA_ARGS__ 7 | #else 8 | # define PX_RENDER_BACKEND_GL //< Default 9 | # define SOKOL_WIN32_NO_GL_LOADER 10 | # define SOKOL_GLCORE33 11 | #define GLSL(...) "#version 330\n" #__VA_ARGS__ 12 | // OpenGL + px_render 13 | # include "deps/glad.c" 14 | #endif 15 | 16 | // gb_math & sokol_app (Window Support) 17 | #define GB_MATH_IMPLEMENTATION 18 | #define SOKOL_IMPL 19 | #include "deps/gb_math.h" 20 | #include "deps/sokol_app.h" 21 | 22 | #define PX_RENDER_IMPLEMENTATION 23 | #include "../px_render.h" 24 | 25 | using namespace px_render; 26 | 27 | namespace Geometry { 28 | float vertex_data[] = { 29 | -1.0, -1.0, 0.0, 1.0, 0.0, 0.0, 30 | 1.0, -1.0, 0.0, 0.0, 1.0, 0.0, 31 | 0.0, 0.8, 0.0, 0.0, 0.0, 1.0, 32 | }; 33 | 34 | uint16_t index_data[] = { 35 | 0, 1, 2, 36 | }; 37 | } 38 | 39 | struct { 40 | RenderContext ctx; 41 | Mat4 proj; 42 | Mat4 view; 43 | Pipeline material; 44 | Buffer vertex_buff; 45 | Buffer index_buff; 46 | } State = {}; 47 | 48 | void init() { 49 | #ifdef PX_RENDER_BACKEND_GL 50 | gladLoadGL(); 51 | #endif 52 | State.ctx.init(); 53 | gb_mat4_perspective((gbMat4*)&State.proj, gb_to_radians(45.f), 1024/(float)768, 0.05f, 900.0f); 54 | gb_mat4_look_at((gbMat4*)&State.view, {0.f,0.0f,3.f}, {0.f,0.f,0.0f}, {0.f,1.f,0.f}); 55 | 56 | State.vertex_buff = State.ctx.createBuffer({BufferType::Vertex, sizeof(Geometry::vertex_data), Usage::Static}); 57 | State.index_buff = State.ctx.createBuffer({BufferType::Index, sizeof(Geometry::index_data), Usage::Static}); 58 | 59 | Pipeline::Info pipeline_info; 60 | pipeline_info.shader.vertex = GLSL( 61 | uniform mat4 u_viewProjection; 62 | in vec3 position; 63 | in vec3 color; 64 | out vec3 v_color; 65 | void main() { 66 | gl_Position = u_viewProjection * vec4(position,1.0); 67 | v_color = color; 68 | } 69 | ); 70 | pipeline_info.shader.fragment = GLSL( 71 | in vec3 v_color; 72 | out vec4 color_out; 73 | void main() { 74 | color_out = vec4(v_color,1.0); 75 | } 76 | ); 77 | pipeline_info.attribs[0] = {"position", VertexFormat::Float3}; 78 | pipeline_info.attribs[1] = {"color", VertexFormat::Float3}; 79 | State.material = State.ctx.createPipeline(pipeline_info); 80 | 81 | // upload data 82 | DisplayList dl; 83 | dl.fillBufferCommand() 84 | .set_buffer(State.vertex_buff) 85 | .set_data(Geometry::vertex_data) 86 | .set_size(sizeof(Geometry::vertex_data)); 87 | dl.fillBufferCommand() 88 | .set_buffer(State.index_buff) 89 | .set_data(Geometry::index_data) 90 | .set_size(sizeof(Geometry::index_data)); 91 | 92 | State.ctx.submitDisplayList(std::move(dl)); 93 | } 94 | 95 | void frame() { 96 | DisplayList dl; 97 | dl.setupViewCommand() 98 | .set_viewport({0,0,1024,768}) 99 | .set_projection_matrix(State.proj) 100 | .set_view_matrix(State.view) 101 | ; 102 | dl.clearCommand() 103 | .set_color({0.5f,0.7f,0.8f,1.0f}) 104 | .set_clear_color(true) 105 | .set_clear_depth(true) 106 | ; 107 | dl.setupPipelineCommand() 108 | .set_pipeline(State.material) 109 | .set_buffer(0,State.vertex_buff) 110 | ; 111 | dl.renderCommand() 112 | .set_index_buffer(State.index_buff) 113 | .set_count(3) 114 | .set_type(IndexFormat::UInt16) 115 | ; 116 | State.ctx.submitDisplayListAndSwap(std::move(dl)); 117 | 118 | // GPU Render... 119 | while(true) { 120 | RenderContext::Result::Enum result = State.ctx.executeOnGPU(); 121 | if (result != RenderContext::Result::OK) break; 122 | } 123 | } 124 | 125 | void cleanup() { 126 | State.ctx.finish(); 127 | } 128 | 129 | sapp_desc sokol_main(int argc, char **argv) { 130 | sapp_desc d = {}; 131 | d.init_cb = init; 132 | d.frame_cb = frame; 133 | d.cleanup_cb = cleanup; 134 | d.width = 1024; 135 | d.height = 768; 136 | d.window_title = "PX-Render Test"; 137 | return d; 138 | } 139 | 140 | -------------------------------------------------------------------------------- /README_px_render.md: -------------------------------------------------------------------------------- 1 | # px_render 2 | Single Header C++ Command Based backend renderer 3 | 4 | Written in C++11/14, with no dependency, and easy to integrate. See a complete example 5 | [HelloWorldTriangle](https://github.com/pplux/px/blob/master/examples/px_render_example_triangle.cpp), [RenderToTexture](https://github.com/pplux/px/blob/master/examples/px_render_example_rtt.cpp). 6 | 7 | The API itself is render agnostic, and will support multiple backends in the future (OpenGL, Vulkan, DX,...) but right now it only supports OpenGL to start with. 8 | 9 | ## Goals: 10 | * Multithreaded Rendering, designed to be used in multithreded applications 11 | * Command Based Rendering, allows to build the render commands in advance in a separte thread 12 | * Reentrant API, everything is held in a 'Context' class, making very easy to support multiple windows, and so on 13 | * No dependency on other libraries, portable and easy to extend 14 | * Implicit task graph dependency for single or groups of tasks 15 | 16 | Thanks to [bgfx](https://github.com/bkaradzic/bgfx) and [sokol_gfx.h](https://github.com/floooh/sokol/blob/master/sokol_gfx.h) they've been a great inspiration, you can read about why px_render and how it compares to bgfx and sokol_gfx [here](https://pplux.github.io/why_px_render.html). 17 | 18 | ## API: 19 | 20 | Example of different parts of the API extracted from the examples, not meant to be compilable as it is, this is just an illustration. 21 | 22 | ```cpp 23 | 24 | using namespace px_render; 25 | 26 | namespace Geometry { 27 | float vertex_data[] = { 28 | -1.0, -1.0, 0.0, 1.0, 0.0, 0.0, 29 | 1.0, -1.0, 0.0, 0.0, 1.0, 0.0, 30 | 0.0, 0.8, 0.0, 0.0, 0.0, 1.0, 31 | }; 32 | 33 | uint16_t index_data[] = { 34 | 0, 1, 2, 35 | }; 36 | } 37 | 38 | struct { 39 | RenderContext ctx; 40 | Mat4 proj; 41 | Mat4 view; 42 | Pipeline material; 43 | Buffer vertex_buff; 44 | Buffer index_buff; 45 | } State = {}; 46 | 47 | void init() { 48 | State.ctx.init(); 49 | gb_mat4_perspective((gbMat4*)&State.proj, gb_to_radians(45.f), 1024/(float)768, 0.05f, 900.0f); 50 | gb_mat4_look_at((gbMat4*)&State.view, {0.f,0.0f,3.f}, {0.f,0.f,0.0f}, {0.f,1.f,0.f}); 51 | 52 | State.vertex_buff = State.ctx.createBuffer({BufferType::Vertex, sizeof(Geometry::vertex_data), Usage::Static}); 53 | State.index_buff = State.ctx.createBuffer({BufferType::Index, sizeof(Geometry::index_data), Usage::Static}); 54 | 55 | Pipeline::Info pipeline_info; 56 | pipeline_info.shader.vertex = GLSL( 57 | uniform mat4 u_viewProjection; 58 | in vec3 position; 59 | in vec3 color; 60 | out vec3 v_color; 61 | void main() { 62 | gl_Position = u_viewProjection * vec4(position,1.0); 63 | v_color = color; 64 | } 65 | ); 66 | pipeline_info.shader.fragment = GLSL( 67 | in vec3 v_color; 68 | out vec4 color_out; 69 | void main() { 70 | color_out = vec4(v_color,1.0); 71 | } 72 | ); 73 | pipeline_info.attribs[0] = {"position", VertexFormat::Float3}; 74 | pipeline_info.attribs[1] = {"color", VertexFormat::Float3}; 75 | State.material = State.ctx.createPipeline(pipeline_info); 76 | 77 | // upload data 78 | DisplayList dl; 79 | dl.fillBufferCommand() 80 | .set_buffer(State.vertex_buff) 81 | .set_data(Geometry::vertex_data) 82 | .set_size(sizeof(Geometry::vertex_data)); 83 | dl.fillBufferCommand() 84 | .set_buffer(State.index_buff) 85 | .set_data(Geometry::index_data) 86 | .set_size(sizeof(Geometry::index_data)); 87 | 88 | State.ctx.submitDisplayList(std::move(dl)); 89 | } 90 | 91 | void frame() { 92 | DisplayList dl; 93 | dl.setupViewCommand() 94 | .set_viewport({0,0,1024,768}) 95 | .set_projection_matrix(State.proj) 96 | .set_view_matrix(State.view) 97 | ; 98 | dl.clearCommand() 99 | .set_color({0.5f,0.7f,0.8f,1.0f}) 100 | .set_clear_color(true) 101 | .set_clear_depth(true) 102 | ; 103 | dl.setupPipelineCommand() 104 | .set_pipeline(State.material) 105 | .set_buffer(0,State.vertex_buff) 106 | ; 107 | dl.renderCommand() 108 | .set_index_buffer(State.index_buff) 109 | .set_count(3) 110 | .set_type(IndexFormat::UInt16) 111 | ; 112 | State.ctx.submitDisplayListAndSwap(std::move(dl)); 113 | 114 | // GPU Render... 115 | while(true) { 116 | RenderContext::Result::Enum result = State.ctx.executeOnGPU(); 117 | if (result != RenderContext::Result::OK) break; 118 | } 119 | } 120 | 121 | ``` 122 | 123 | *WIP* *WIP* *WIP* *WIP* *WIP* *WIP* 124 | 125 | 126 | ## TODO's 127 | * [ ] improve documentation 128 | * [ ] More tests! and examples 129 | * [ ] Proper stencil support 130 | * [ ] Compute shaders support 131 | * [ ] WebGL, RaspberryPI, etc.., it should work 132 | * [ ] Add Vulkan, DX render, other backends 133 | -------------------------------------------------------------------------------- /README_px_sched.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/pplux/px_sched.svg?branch=master)](https://travis-ci.org/pplux/px_sched) 2 | [![Build Status AppVeyor](https://ci.appveyor.com/api/projects/status/github/pplux/px_sched)](https://ci.appveyor.com/project/pplux/px-sched) 3 | 4 | # px_sched 5 | Single Header C++ Task Scheduler 6 | 7 | Written in C++11(only for thread API), with no dependency, and easy to integrate. See the examples: 8 | [ex1.cpp](https://github.com/pplux/px/blob/master/examples/px_sched_example1.cpp), 9 | [ex2.cpp](https://github.com/pplux/px/blob/master/examples/px_sched_example2.cpp), 10 | [ex3.cpp](https://github.com/pplux/px/blob/master/examples/px_sched_example3.cpp), 11 | [ex4.cpp](https://github.com/pplux/px/blob/master/examples/px_sched_example4.cpp), 12 | [ex5.cpp](https://github.com/pplux/px/blob/master/examples/px_sched_example5.cpp), 13 | [ex6.cpp](https://github.com/pplux/px/blob/master/examples/px_sched_example6.cpp), 14 | [ex7.cpp](https://github.com/pplux/px/blob/master/examples/px_sched_example7.cpp). 15 | 16 | 17 | 18 | ## Goals: 19 | * Allow task oriented multihtread programmming without mutexes, locks, condition variables... 20 | * Implicit task graph dependency for single or groups of tasks 21 | * Portable, written in C++11 with no dependency 22 | * C++11 only used for thread, mutex, and condition variable *no STL container is used* 23 | * Easy to use, flexible, and lightweight 24 | * Inspied by Naughty Dog's talk [Parallelizing the Naughty Dog Engine](https://www.gdcvault.com/play/1022186/Parallelizing-the-Naughty-Dog-Engine), [enkiTS](https://github.com/dougbinks/enkiTS), and [STB's single-file libraries](https://github.com/nothings/stb) 25 | * No dynamic memory allocations, all memory is allocated at [initialization](https://github.com/pplux/px_sched/blob/4b84af4a1ffb1a06bbf70f3d70301ca26357fba8/px_sched.h#L112). Easy to integrate with your own memory manager if needed. 26 | 27 | ## API: 28 | 29 | ```cpp 30 | int main(int, char **) { 31 | px::Scheduler schd; 32 | schd.init(); 33 | 34 | px::Sync s1,s2,s3; 35 | 36 | // First we launch a group of 10 concurrent tasks, all of them referencing the same sync object (s1) 37 | for(size_t i = 0; i < 10; ++i) { 38 | auto job = [i] { 39 | printf("Phase 1: Task %zu completed from %s\n", 40 | i, px::Scheduler::current_thread_name()); 41 | }; 42 | schd.run(job, &s1); 43 | } 44 | 45 | // Another set of 10 concurrent tasks will be launched, once the first group (s1) finishes. 46 | // The second group will be attached to the second Sync object (s2). 47 | for(size_t i = 0; i < 10; ++i) { 48 | auto job = [i] { 49 | printf("Phase 2: Task %zu completed from %s\n", 50 | i, px::Scheduler::current_thread_name()); 51 | }; 52 | schd.runAfter(s1, job, &s2); 53 | } 54 | 55 | // Finally another group, waiting for the second (s2) fo finish. 56 | for(size_t i = 0; i < 10; ++i) { 57 | auto job = [i] { 58 | printf("Phase 3: Task %zu completed from %s\n", 59 | i, px::Scheduler::current_thread_name()); 60 | }; 61 | schd.runAfter(s2, job, &s3); 62 | } 63 | 64 | // Sync objects can be copied, internally they are just a handle. 65 | px::Sync last = s3; 66 | 67 | // The main thread will now wait for the last task to finish, the task graph is infered by the 68 | // usage of sync objects and no wait has ocurred on the main thread until now. 69 | printf("Waiting for tasks to finish...\n"); 70 | schd.waitFor(last); // wait for all tasks to finish 71 | printf("Waiting for tasks to finish...DONE \n"); 72 | 73 | return 0; 74 | } 75 | ``` 76 | 77 | `px::Scheduler` is the main object, normally you should only instance one, but 78 | that's up to you. There are two methods to launch tasks: 79 | 80 | * `run(const px::Job &job, px::Sync *optional_sync_object = nullptr)` 81 | * `runAfter(const px::Sync trigger, const px::Job &job, px::Sync *out_optional_sync_obj = nullptr)` 82 | 83 | Both run methods receive a `Job` object, by default it is a `std::function` but you can [customize](https://github.com/pplux/px_sched/blob/master/examples/example2.cpp) to fit your needs. 84 | 85 | Both run methods receive an optional output argument, a `Sync` object. `Sync` objects are used to coordinate dependencies between groups of tasks (or single tasks). The simplest case is to wait for a group of tasks to finish: 86 | 87 | ```cpp 88 | px::Sync s; 89 | for(size_t i = 0; i < 128; ++i) { 90 | schd.run([i]{printf("Task %zu\n",i);}, &s); 91 | } 92 | schd.waitFor(s); 93 | ``` 94 | 95 | `Sync` objects can also be used to launch tasks when a group of tasks have finished with the method `px::Scheduler::runAfter`: 96 | 97 | ```cpp 98 | px::Sync s; 99 | for(size_t i = 0; i < 128; ++i) { 100 | schd.run([i]{printf("Task %zu\n",i);}, &s); 101 | } 102 | px::Sync last; 103 | schd.runAfter(s, []{printf("Last Task\n");}, last); 104 | 105 | schd.waitFor(last); 106 | ``` 107 | 108 | ## TODO's 109 | * [ ] improve documentation 110 | * [ ] Add support for Windows Fibers on windows 111 | * [ ] Add support for ucontext on Posix 112 | -------------------------------------------------------------------------------- /examples/common/render_common.h: -------------------------------------------------------------------------------- 1 | // Define PX_RENDER_DEBUG to enable internal debug 2 | // #define PX_RENDER_DEBUG 3 | // #define PX_RENDER_DEBUG_LEVEL 100 4 | 5 | // Emscripten config 6 | #ifdef __EMSCRIPTEN__ 7 | // Compile with emcc --std=c++14 -s USE_WEBGL2=1 px_render_example_rtt.cpp -o rtt.html 8 | # define SOKOL_GLES3 9 | # define PX_RENDER_BACKEND_GLES 10 | #else 11 | # define PX_RENDER_BACKEND_GL //< Default 12 | # define SOKOL_WIN32_NO_GL_LOADER 13 | # define SOKOL_GLCORE33 14 | // OpenGL + px_render 15 | # include "deps/glad.c" 16 | #endif 17 | 18 | // Helper macro to include GLSL code... (for the examples) 19 | #ifndef GLSL 20 | # if defined(PX_RENDER_BACKEND_GLES) 21 | # define GLSL(...) "#version 300 es\n precision highp float;\n" #__VA_ARGS__ 22 | # elif defined(PX_RENDER_BACKEND_GL) 23 | # define GLSL(...) "#version 330\n" #__VA_ARGS__ 24 | # else 25 | # error No px_render backend selected 26 | # endif 27 | #endif 28 | 29 | #include "../deps/imgui.h" 30 | 31 | // gb_math, sokol, px_render & px_sched implementations 32 | #define GB_MATH_IMPLEMENTATION 33 | #define SOKOL_IMPL 34 | #define SOKOL_WIN32_FORCE_MAIN 35 | #include "../deps/gb_math.h" 36 | #include "../deps/sokol_app.h" 37 | 38 | #ifdef Always 39 | #undef Always // linux: X11.h --> WTF 40 | #undef None // linux: X11.h --> WTF 41 | #endif 42 | 43 | #define PX_RENDER_IMPLEMENTATION 44 | #define PX_RENDER_IMGUI_IMPLEMENTATION 45 | #define PX_SCHED_IMPLEMENTATION 46 | #include "../../px_render.h" 47 | #include "../../px_sched.h" 48 | #include "../../px_render_imgui.h" 49 | 50 | void init(px_render::RenderContext *ctx, px_sched::Scheduler *sched); 51 | void finish(px_render::RenderContext *ctx, px_sched::Scheduler *sched); 52 | void render(px_render::RenderContext *ctx, px_sched::Scheduler *sched); 53 | 54 | #include "../deps/imgui.cpp" 55 | #include "../deps/imgui_draw.cpp" 56 | #include "../deps/imgui_widgets.cpp" 57 | 58 | struct { 59 | px_render::RenderContext ctx; 60 | px_sched::Scheduler sched; 61 | px_sched::Sync frame; 62 | ImGuiContext *imgui_ctx = nullptr; 63 | bool btn_down[SAPP_MAX_MOUSEBUTTONS]; 64 | bool btn_up[SAPP_MAX_MOUSEBUTTONS]; 65 | } WinState; 66 | 67 | void setup_input() { 68 | auto& io = ImGui::GetIO(); 69 | io.KeyMap[ImGuiKey_Tab] = SAPP_KEYCODE_TAB; 70 | io.KeyMap[ImGuiKey_LeftArrow] = SAPP_KEYCODE_LEFT; 71 | io.KeyMap[ImGuiKey_RightArrow] = SAPP_KEYCODE_RIGHT; 72 | io.KeyMap[ImGuiKey_UpArrow] = SAPP_KEYCODE_UP; 73 | io.KeyMap[ImGuiKey_DownArrow] = SAPP_KEYCODE_DOWN; 74 | io.KeyMap[ImGuiKey_PageUp] = SAPP_KEYCODE_PAGE_UP; 75 | io.KeyMap[ImGuiKey_PageDown] = SAPP_KEYCODE_PAGE_DOWN; 76 | io.KeyMap[ImGuiKey_Home] = SAPP_KEYCODE_HOME; 77 | io.KeyMap[ImGuiKey_End] = SAPP_KEYCODE_END; 78 | io.KeyMap[ImGuiKey_Delete] = SAPP_KEYCODE_DELETE; 79 | io.KeyMap[ImGuiKey_Backspace] = SAPP_KEYCODE_BACKSPACE; 80 | io.KeyMap[ImGuiKey_Space] = SAPP_KEYCODE_SPACE; 81 | io.KeyMap[ImGuiKey_Enter] = SAPP_KEYCODE_ENTER; 82 | io.KeyMap[ImGuiKey_Escape] = SAPP_KEYCODE_ESCAPE; 83 | io.KeyMap[ImGuiKey_A] = SAPP_KEYCODE_A; 84 | io.KeyMap[ImGuiKey_C] = SAPP_KEYCODE_C; 85 | io.KeyMap[ImGuiKey_V] = SAPP_KEYCODE_V; 86 | io.KeyMap[ImGuiKey_X] = SAPP_KEYCODE_X; 87 | io.KeyMap[ImGuiKey_Y] = SAPP_KEYCODE_Y; 88 | io.KeyMap[ImGuiKey_Z] = SAPP_KEYCODE_Z; 89 | } 90 | 91 | void init() { 92 | WinState.sched.init(); 93 | #ifdef PX_RENDER_BACKEND_GL 94 | gladLoadGL(); 95 | #endif 96 | WinState.ctx.init(); 97 | WinState.imgui_ctx = ImGui::CreateContext(); 98 | ImGui_Impl_pxrender_Init(&WinState.ctx); 99 | setup_input(); 100 | 101 | init(&WinState.ctx, &WinState.sched); 102 | } 103 | 104 | void frame() { 105 | WinState.sched.waitFor(WinState.frame); 106 | WinState.sched.run([] { 107 | ImGuiIO &io = ImGui::GetIO(); 108 | io.DisplaySize = {(float)sapp_width(), (float)sapp_height()}; 109 | 110 | for (int i = 0; i < SAPP_MAX_MOUSEBUTTONS; i++) { 111 | if (WinState.btn_down[i]) { 112 | WinState.btn_down[i] = false; 113 | io.MouseDown[i] = true; 114 | } 115 | else if (WinState.btn_up[i]) { 116 | WinState.btn_up[i] = false; 117 | io.MouseDown[i] = false; 118 | } 119 | } 120 | 121 | render(&WinState.ctx, &WinState.sched); 122 | } , &WinState.frame); 123 | 124 | // GPU Render... until swap is requested 125 | while(WinState.ctx.executeOnGPU() == px_render::RenderContext::Result::OK) {}; 126 | } 127 | 128 | void cleanup() { 129 | finish(&WinState.ctx, &WinState.sched); 130 | ImGui_Impl_pxrender_Shutdown(); 131 | WinState.ctx.finish(); 132 | WinState.imgui_ctx = nullptr; 133 | } 134 | 135 | void input(const sapp_event* event) { 136 | auto& io = ImGui::GetIO(); 137 | io.KeyAlt = (event->modifiers & SAPP_MODIFIER_ALT) != 0; 138 | io.KeyCtrl = (event->modifiers & SAPP_MODIFIER_CTRL) != 0; 139 | io.KeyShift = (event->modifiers & SAPP_MODIFIER_SHIFT) != 0; 140 | io.KeySuper = (event->modifiers & SAPP_MODIFIER_SUPER) != 0; 141 | switch (event->type) { 142 | case SAPP_EVENTTYPE_MOUSE_DOWN: 143 | io.MousePos.x = event->mouse_x; 144 | io.MousePos.y = event->mouse_y; 145 | WinState.btn_down[event->mouse_button] = true; 146 | break; 147 | case SAPP_EVENTTYPE_MOUSE_UP: 148 | io.MousePos.x = event->mouse_x; 149 | io.MousePos.y = event->mouse_y; 150 | WinState.btn_up[event->mouse_button] = true; 151 | break; 152 | case SAPP_EVENTTYPE_MOUSE_MOVE: 153 | io.MousePos.x = event->mouse_x; 154 | io.MousePos.y = event->mouse_y; 155 | break; 156 | case SAPP_EVENTTYPE_MOUSE_ENTER: 157 | case SAPP_EVENTTYPE_MOUSE_LEAVE: 158 | for (int i = 0; i < 3; i++) { 159 | WinState.btn_down[i] = false; 160 | WinState.btn_up[i] = false; 161 | io.MouseDown[i] = false; 162 | } 163 | break; 164 | case SAPP_EVENTTYPE_MOUSE_SCROLL: 165 | io.MouseWheelH = event->scroll_x; 166 | io.MouseWheel = event->scroll_y; 167 | break; 168 | case SAPP_EVENTTYPE_TOUCHES_BEGAN: 169 | WinState.btn_down[0] = true; 170 | io.MousePos.x = event->touches[0].pos_x; 171 | io.MousePos.y = event->touches[0].pos_y; 172 | break; 173 | case SAPP_EVENTTYPE_TOUCHES_MOVED: 174 | io.MousePos.x = event->touches[0].pos_x; 175 | io.MousePos.y = event->touches[0].pos_y; 176 | break; 177 | case SAPP_EVENTTYPE_TOUCHES_ENDED: 178 | WinState.btn_up[0] = true; 179 | io.MousePos.x = event->touches[0].pos_x; 180 | io.MousePos.y = event->touches[0].pos_y; 181 | break; 182 | case SAPP_EVENTTYPE_TOUCHES_CANCELLED: 183 | WinState.btn_up[0] = WinState.btn_down[0] = false; 184 | break; 185 | case SAPP_EVENTTYPE_KEY_DOWN: 186 | io.KeysDown[event->key_code] = true; 187 | break; 188 | case SAPP_EVENTTYPE_KEY_UP: 189 | io.KeysDown[event->key_code] = false; 190 | break; 191 | case SAPP_EVENTTYPE_CHAR: 192 | io.AddInputCharacter((ImWchar)event->char_code); 193 | break; 194 | default: 195 | break; 196 | } 197 | } 198 | 199 | sapp_desc sokol_main(int argc, char **argv) { 200 | sapp_desc d = {}; 201 | d.init_cb = init; 202 | d.frame_cb = frame; 203 | d.cleanup_cb = cleanup; 204 | d.event_cb = input; 205 | d.width = 1024; 206 | d.height = 768; 207 | d.high_dpi = true; 208 | d.window_title = "PX-Render Test"; 209 | setup_input; 210 | return d; 211 | } 212 | -------------------------------------------------------------------------------- /examples/deps/imconfig.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // COMPILE-TIME OPTIONS FOR DEAR IMGUI 3 | // Runtime options (clipboard callbacks, enabling various features, etc.) can generally be set via the ImGuiIO structure. 4 | // You can use ImGui::SetAllocatorFunctions() before calling ImGui::CreateContext() to rewire memory allocation functions. 5 | //----------------------------------------------------------------------------- 6 | // A) You may edit imconfig.h (and not overwrite it when updating Dear ImGui, or maintain a patch/rebased branch with your modifications to it) 7 | // B) or '#define IMGUI_USER_CONFIG "my_imgui_config.h"' in your project and then add directives in your own file without touching this template. 8 | //----------------------------------------------------------------------------- 9 | // You need to make sure that configuration settings are defined consistently _everywhere_ Dear ImGui is used, which include the imgui*.cpp 10 | // files but also _any_ of your code that uses Dear ImGui. This is because some compile-time options have an affect on data structures. 11 | // Defining those options in imconfig.h will ensure every compilation unit gets to see the same data structure layouts. 12 | // Call IMGUI_CHECKVERSION() from your .cpp files to verify that the data structures your files are using are matching the ones imgui.cpp is using. 13 | //----------------------------------------------------------------------------- 14 | 15 | #pragma once 16 | 17 | //---- Define assertion handler. Defaults to calling assert(). 18 | // If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement. 19 | //#define IM_ASSERT(_EXPR) MyAssert(_EXPR) 20 | //#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts 21 | 22 | //---- Define attributes of all API symbols declarations, e.g. for DLL under Windows 23 | // Using dear imgui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility. 24 | //#define IMGUI_API __declspec( dllexport ) 25 | //#define IMGUI_API __declspec( dllimport ) 26 | 27 | //---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to avoid using soon-to-be obsolete function/names. 28 | //#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS 29 | 30 | //---- Disable all of Dear ImGui or don't implement standard windows. 31 | // It is very strongly recommended to NOT disable the demo windows during development. Please read comments in imgui_demo.cpp. 32 | //#define IMGUI_DISABLE // Disable everything: all headers and source files will be empty. 33 | //#define IMGUI_DISABLE_DEMO_WINDOWS // Disable demo windows: ShowDemoWindow()/ShowStyleEditor() will be empty. Not recommended. 34 | //#define IMGUI_DISABLE_METRICS_WINDOW // Disable debug/metrics window: ShowMetricsWindow() will be empty. 35 | 36 | //---- Don't implement some functions to reduce linkage requirements. 37 | //#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // [Win32] Don't implement default clipboard handler. Won't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. 38 | //#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] Don't implement default IME handler. Won't use and link with ImmGetContext/ImmSetCompositionWindow. 39 | //#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, ime). 40 | //#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default). 41 | //#define IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself (e.g. if you don't want to link with vsnprintf) 42 | //#define IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 so you can implement them yourself. 43 | //#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function. 44 | //#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions(). 45 | 46 | //---- Include imgui_user.h at the end of imgui.h as a convenience 47 | //#define IMGUI_INCLUDE_IMGUI_USER_H 48 | 49 | //---- Pack colors to BGRA8 instead of RGBA8 (to avoid converting from one to another) 50 | //#define IMGUI_USE_BGRA_PACKED_COLOR 51 | 52 | //---- Use 32-bit for ImWchar (default is 16-bit) to support full unicode code points. 53 | //#define IMGUI_USE_WCHAR32 54 | 55 | //---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version 56 | // By default the embedded implementations are declared static and not available outside of imgui cpp files. 57 | //#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h" 58 | //#define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h" 59 | //#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION 60 | //#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION 61 | 62 | //---- Unless IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS is defined, use the much faster STB sprintf library implementation of vsnprintf instead of the one from the default C library. 63 | // Note that stb_sprintf.h is meant to be provided by the user and available in the include path at compile time. Also, the compatibility checks of the arguments and formats done by clang and GCC will be disabled in order to support the extra formats provided by STB sprintf. 64 | // #define IMGUI_USE_STB_SPRINTF 65 | 66 | //---- Define constructor and implicit cast operators to convert back<>forth between your math types and ImVec2/ImVec4. 67 | // This will be inlined as part of ImVec2 and ImVec4 class declarations. 68 | /* 69 | #define IM_VEC2_CLASS_EXTRA \ 70 | ImVec2(const MyVec2& f) { x = f.x; y = f.y; } \ 71 | operator MyVec2() const { return MyVec2(x,y); } 72 | 73 | #define IM_VEC4_CLASS_EXTRA \ 74 | ImVec4(const MyVec4& f) { x = f.x; y = f.y; z = f.z; w = f.w; } \ 75 | operator MyVec4() const { return MyVec4(x,y,z,w); } 76 | */ 77 | 78 | //---- Use 32-bit vertex indices (default is 16-bit) is one way to allow large meshes with more than 64K vertices. 79 | // Your renderer backend will need to support it (most example renderer backends support both 16/32-bit indices). 80 | // Another way to allow large meshes while keeping 16-bit indices is to handle ImDrawCmd::VtxOffset in your renderer. 81 | // Read about ImGuiBackendFlags_RendererHasVtxOffset for details. 82 | //#define ImDrawIdx unsigned int 83 | 84 | //---- Override ImDrawCallback signature (will need to modify renderer backends accordingly) 85 | //struct ImDrawList; 86 | //struct ImDrawCmd; 87 | //typedef void (*MyImDrawCallback)(const ImDrawList* draw_list, const ImDrawCmd* cmd, void* my_renderer_user_data); 88 | //#define ImDrawCallback MyImDrawCallback 89 | 90 | //---- Debug Tools: Macro to break in Debugger 91 | // (use 'Metrics->Tools->Item Picker' to pick widgets with the mouse and break into them for easy debugging.) 92 | //#define IM_DEBUG_BREAK IM_ASSERT(0) 93 | //#define IM_DEBUG_BREAK __debugbreak() 94 | 95 | //---- Debug Tools: Have the Item Picker break in the ItemAdd() function instead of ItemHoverable(), 96 | // (which comes earlier in the code, will catch a few extra items, allow picking items other than Hovered one.) 97 | // This adds a small runtime cost which is why it is not enabled by default. 98 | //#define IMGUI_DEBUG_TOOL_ITEM_PICKER_EX 99 | 100 | //---- Debug Tools: Enable slower asserts 101 | //#define IMGUI_DEBUG_PARANOID 102 | 103 | //---- Tip: You can add extra functions within the ImGui:: namespace, here or in your own headers files. 104 | /* 105 | namespace ImGui 106 | { 107 | void MyFunction(const char* name, const MyMatrix44& v); 108 | } 109 | */ 110 | -------------------------------------------------------------------------------- /px_render_imgui.h: -------------------------------------------------------------------------------- 1 | // ImGui Renderer for: px_render 2 | // USAGE 3 | // 4 | // In *ONE* C++ file you need to declare 5 | // #define PX_RENDER_IMGUI_IMPLEMENTATION 6 | // before including the file that contains px_render_imgui.h 7 | // 8 | // px_render_imgui must be included *AFTER* px_render and imgui.h 9 | // 10 | // CHANGELOG 11 | // (minor and older changes stripped away, please see git history for details) 12 | // 2018-07-09: Initial version 13 | // 2018-08-22: Refactorized into a single-header plugin 14 | 15 | namespace px_render { 16 | struct RenderContext; 17 | struct DisplayList; 18 | } 19 | 20 | IMGUI_IMPL_API void ImGui_Impl_pxrender_Init(px_render::RenderContext *ctx); 21 | IMGUI_IMPL_API void ImGui_Impl_pxrender_Shutdown(); 22 | IMGUI_IMPL_API void ImGui_Impl_pxrender_RenderDrawData(ImDrawData* draw_data, px_render::DisplayList *dl_output); 23 | 24 | //-- IMPLEMENTATION ---------------------------------------------------------- 25 | 26 | #ifdef PX_RENDER_IMGUI_IMPLEMENTATION 27 | 28 | #ifndef PX_RENDER 29 | #error px_render must be included before px_render_imgui (because imgui plugin does not include px_render.h) 30 | #endif 31 | 32 | #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) 33 | #define _CRT_SECURE_NO_WARNINGS 34 | #endif 35 | 36 | #include 37 | 38 | static struct { 39 | px_render::RenderContext *ctx = nullptr; 40 | px_render::Pipeline pipeline; 41 | px_render::Texture font; 42 | px_render::Buffer vertex; 43 | px_render::Buffer index; 44 | uint32_t vertex_size = 0; 45 | uint32_t index_size = 0; 46 | } PRS; // Px-Render-State 47 | 48 | 49 | #ifndef PX_RENDER_IMGUI_GLSL 50 | #if defined(PX_RENDER_BACKEND_GLES) 51 | # define PX_RENDER_IMGUI_GLSL(...) "#version 300 es\n precision highp float;\n" #__VA_ARGS__ 52 | #elif defined(PX_RENDER_BACKEND_GL) 53 | #define PX_RENDER_IMGUI_GLSL(...) "#version 330\n" #__VA_ARGS__ 54 | #else 55 | #error No px_render backend selected 56 | #endif 57 | #endif 58 | void ImGui_Impl_pxrender_Init(px_render::RenderContext *ctx) { 59 | using namespace px_render; 60 | assert(PRS.ctx == nullptr && "ImGUI_Impl_pxrender_Init() called twice"); 61 | PRS.ctx = ctx; 62 | 63 | // -- Create Pipeline Object --------------------------------------------- 64 | px_render::Pipeline::Info pinfo; 65 | pinfo.shader.vertex = PX_RENDER_IMGUI_GLSL( 66 | uniform mat4 u_projection; 67 | in vec2 pos; 68 | in vec2 uv; 69 | in vec4 color; 70 | out vec2 frag_uv; 71 | out vec4 frag_color; 72 | void main() { 73 | frag_uv = uv; 74 | frag_color = color; 75 | gl_Position = u_projection*vec4(pos, 0.0, 1.0); 76 | } 77 | ); 78 | pinfo.shader.fragment = PX_RENDER_IMGUI_GLSL( 79 | uniform sampler2D u_tex0; 80 | in vec2 frag_uv; 81 | in vec4 frag_color; 82 | out vec4 color; 83 | void main() { 84 | color = texture(u_tex0, frag_uv)*frag_color; 85 | } 86 | ); 87 | pinfo.attribs[0] = {"pos", VertexFormat::Float2}; 88 | pinfo.attribs[1] = {"uv", VertexFormat::Float2}; 89 | pinfo.attribs[2] = {"color", VertexFormat::UInt8 | VertexFormat::NumComponents4 | VertexFormat::Normalized}; 90 | pinfo.textures[0] = TextureType::T2D; 91 | pinfo.blend.enabled = true; 92 | pinfo.blend.op_rgb = pinfo.blend.op_alpha = BlendOp::Add; 93 | pinfo.blend.src_rgb = pinfo.blend.src_alpha = BlendFactor::SrcAlpha; 94 | pinfo.blend.dst_rgb = pinfo.blend.dst_alpha = BlendFactor::OneMinusSrcAlpha; 95 | pinfo.depth_func = CompareFunc::Always; 96 | pinfo.cull = Cull::Disabled; 97 | pinfo.depth_write = false; 98 | PRS.pipeline = PRS.ctx->createPipeline(pinfo); 99 | 100 | // -- Default font texture ----------------------------------------------- 101 | int font_width, font_height; 102 | unsigned char *font_pixels; 103 | ::ImGui::GetIO().Fonts->GetTexDataAsRGBA32(&font_pixels, &font_width, &font_height); 104 | Texture::Info tinfo; 105 | tinfo.width = font_width; 106 | tinfo.height = font_height; 107 | tinfo.format = TexelsFormat::RGBA_U8; 108 | PRS.font = PRS.ctx->createTexture(tinfo); 109 | DisplayList tex_dl; 110 | tex_dl.fillTextureCommand() 111 | .set_texture(PRS.font) 112 | .set_data(font_pixels); 113 | PRS.ctx->submitDisplayList(std::move(tex_dl)); 114 | ::ImGui::GetIO().Fonts->TexID = (void*)&PRS.font; 115 | } 116 | 117 | void ImGui_Impl_pxrender_Shutdown() { 118 | using namespace px_render; 119 | DisplayList sdl; 120 | sdl 121 | .destroy(PRS.font) 122 | .destroy(PRS.pipeline) 123 | .destroy(PRS.vertex) 124 | .destroy(PRS.index) 125 | ; 126 | PRS.ctx->submitDisplayList(std::move(sdl)); 127 | PRS.ctx = nullptr; 128 | PRS.vertex = 0; 129 | PRS.index = 0; 130 | } 131 | 132 | void ImGui_Impl_pxrender_RenderDrawData(ImDrawData* draw_data, px_render::DisplayList *dl_output) { 133 | if (!draw_data || !dl_output) return; 134 | 135 | using namespace px_render; 136 | ImGuiIO& io = ::ImGui::GetIO(); 137 | int fb_width = (int)(draw_data->DisplaySize.x * io.DisplayFramebufferScale.x); 138 | int fb_height = (int)(draw_data->DisplaySize.y * io.DisplayFramebufferScale.y); 139 | if (fb_width <= 0 || fb_height <= 0) return; 140 | draw_data->ScaleClipRects(io.DisplayFramebufferScale); 141 | 142 | float L = draw_data->DisplayPos.x; 143 | float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x; 144 | float T = draw_data->DisplayPos.y; 145 | float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y; 146 | const px_render::Mat4 proj = 147 | { 148 | 2.0f/(R-L), 0.0f, 0.0f, 0.0f , 149 | 0.0f, 2.0f/(T-B), 0.0f, 0.0f , 150 | 0.0f, 0.0f, -1.0f, 0.0f , 151 | (R+L)/(L-R), (T+B)/(B-T), 0.0f, 1.0f 152 | }; 153 | dl_output->setupViewCommand() 154 | .set_projection_matrix(proj) 155 | .set_viewport({0,0, (uint16_t) fb_width, (uint16_t) fb_height}); 156 | ; 157 | 158 | ImVec2 pos = draw_data->DisplayPos; 159 | 160 | for(int n = 0; n < draw_data->CmdListsCount; ++n) { 161 | const ImDrawList *cmd_list = draw_data->CmdLists[n]; 162 | uint32_t required_vertex_size = cmd_list->VtxBuffer.Size * sizeof(ImDrawVert); 163 | if (required_vertex_size > PRS.vertex_size) { 164 | auto old = PRS.vertex; 165 | dl_output->destroy(old); 166 | PRS.vertex = PRS.ctx->createBuffer({BufferType::Vertex, required_vertex_size, Usage::Stream}); 167 | PRS.vertex_size = required_vertex_size; 168 | } 169 | dl_output->fillBufferCommand() 170 | .set_buffer(PRS.vertex) 171 | .set_data(cmd_list->VtxBuffer.Data) 172 | .set_size(required_vertex_size) 173 | ; 174 | uint32_t required_index_size = cmd_list->IdxBuffer.Size *sizeof(ImDrawIdx); 175 | if (required_index_size > PRS.index_size) { 176 | auto old = PRS.index; 177 | dl_output->destroy(old); 178 | PRS.index = PRS.ctx->createBuffer({BufferType::Index, required_index_size, Usage::Stream}); 179 | PRS.index_size = required_index_size; 180 | } 181 | dl_output->fillBufferCommand() 182 | .set_buffer(PRS.index) 183 | .set_data(cmd_list->IdxBuffer.Data) 184 | .set_size(required_index_size) 185 | ; 186 | uint32_t idx_buffer_offset = 0; 187 | 188 | for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; ++cmd_i) { 189 | const ImDrawCmd *cmd = &cmd_list->CmdBuffer[cmd_i]; 190 | if (cmd->UserCallback) { 191 | cmd->UserCallback(cmd_list, cmd); 192 | continue; 193 | } 194 | 195 | Vec4 clip_rect = { cmd->ClipRect.x - pos.x, cmd->ClipRect.y - pos.y, cmd->ClipRect.z - pos.x, cmd->ClipRect.w - pos.y}; 196 | if (clip_rect.v.x < fb_width && clip_rect.v.y < fb_height && clip_rect.v.z >= 0.0f && clip_rect.v.w >= 0.0f) { 197 | // invert y on scissor 198 | Vec4 scissor_rect = { clip_rect.f[0], fb_height - clip_rect.f[3], clip_rect.f[2] - clip_rect.f[0], clip_rect.f[3] - clip_rect.f[1]}; 199 | dl_output->setupPipelineCommand() 200 | .set_pipeline(PRS.pipeline) 201 | .set_buffer(0, PRS.vertex) 202 | .set_texture(0, *(Texture*)cmd->TextureId) 203 | .set_scissor(scissor_rect) 204 | ; 205 | dl_output->renderCommand() 206 | .set_index_buffer(PRS.index) 207 | .set_offset(sizeof(ImDrawIdx)*idx_buffer_offset) 208 | .set_count(cmd->ElemCount) 209 | .set_type(IndexFormat::UInt16) 210 | ; 211 | idx_buffer_offset += cmd->ElemCount; 212 | } 213 | } 214 | } 215 | } 216 | 217 | #undef PX_RENDER_IMGUI_GLSL 218 | 219 | #endif // PX_RENDER_IMGUI_IMPLEMENTATION 220 | -------------------------------------------------------------------------------- /px_mem.h: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright (c) 2018-2023 Jose L. Hidalgo (PpluX) 3 | 4 | px_mem.h - Mem management functions 5 | Single header file to handle Mem references, Buffers, unique_ptr/array 6 | alike objects and so on. It offers safer objects to handle pointers to 7 | memory and a RAII implementation for dynamic allocated memory. 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of 10 | this software and associated documentation files (the "Software"), to deal in 11 | the Software without restriction, including without limitation the rights to 12 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 13 | the Software, and to permit persons to whom the Software is furnished to do so, 14 | subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 21 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 22 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 23 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 24 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | ----------------------------------------------------------------------------- */ 26 | 27 | // USAGE 28 | // 29 | // In *ONE* C++ file you need to declare 30 | // #define PX_MEM_IMPLEMENTATION 31 | // before including the file that contains px_mem.h 32 | 33 | 34 | #ifndef PX_MEM 35 | #define PX_MEM 1 36 | 37 | #include 38 | #include 39 | #include //> for std::align 40 | 41 | #ifndef PX_MEM_ASSERT 42 | #include 43 | #define PX_MEM_ASSERT(cnd, msg) assert((cnd) && msg) 44 | #endif 45 | 46 | namespace px { 47 | 48 | // Set this first, before anything else, the default implementation will 49 | // use a wrapper around malloc and free 50 | void SetMemoryFunctions( 51 | void*(*MemAlloc)(size_t size_bytes, size_t alignment), 52 | void (*MemFree)(void*)); 53 | 54 | // Function to adquire memory (with proper alignment) 55 | void *MemoryAlloc(size_t amount, size_t alignment); 56 | 57 | // Function to release a previously adquired memory 58 | void MemoryFree(void *ptr); 59 | 60 | // Class to handle references to array of objects in memory 61 | template 62 | class ConstMemRef { 63 | public: 64 | ConstMemRef() : ptr_(nullptr), count_(0) {} 65 | ConstMemRef(const T* ptr, size_t num) : ptr_(ptr), count_(num) {} 66 | ConstMemRef(const ConstMemRef &) = default; 67 | ConstMemRef& operator=(const ConstMemRef &) = default; 68 | constexpr ConstMemRef(ConstMemRef &&) = default; 69 | ConstMemRef& operator=(ConstMemRef &&) = default; 70 | 71 | const T& operator[](size_t p) const { PX_MEM_ASSERT(p < count_, "Invalid access");return ptr_[p]; } 72 | size_t size() const { return count_; } 73 | size_t sizeInBytes() const { return count_*sizeof(T); } 74 | const T* get() const { return ptr_; } 75 | private: 76 | const T *ptr_; 77 | size_t count_; 78 | }; 79 | 80 | // Mem similar to unique_ptr but with some restrictions to fully implement RAII and 81 | // avoid any new/delete 82 | template 83 | class Mem { 84 | public: 85 | Mem() {} 86 | ~Mem() { reset(); } 87 | 88 | Mem(Mem &&other) { 89 | ptr_ = other.ptr_; 90 | other.ptr_ = nullptr; 91 | } 92 | 93 | Mem& operator=(Mem &&other) { 94 | reset(); 95 | ptr_ = other.ptr_; 96 | other.ptr_ = nullptr; 97 | return *this; 98 | } 99 | 100 | bool valid() const { return ptr_ != nullptr; } 101 | operator bool() const { return valid(); } 102 | 103 | // instance an object of type T 104 | T* alloc() { reset(); ptr_ = new (MemoryAlloc(sizeof(T), alignof(T))) T(); return ptr_; } 105 | 106 | // alloc for derived classes 107 | template 108 | D* alloc() { 109 | reset(); 110 | D *result = new (MemoryAlloc(sizeof(D), alignof(T))) D(); 111 | ptr_ = result; 112 | return result; 113 | } 114 | 115 | void reset() { 116 | if (ptr_) { 117 | ptr_->~T(); 118 | MemoryFree(ptr_); 119 | ptr_ = nullptr; 120 | } 121 | } 122 | 123 | T* get() { return ptr_; } 124 | const T* get() const { return ptr_; } 125 | 126 | T* operator->() { return ptr_; } 127 | const T* operator->() const { return ptr_; } 128 | T& operator*() { return *ptr_; } 129 | const T& operator*() const { return *ptr_; } 130 | 131 | private: 132 | T *ptr_ = nullptr; 133 | }; 134 | 135 | 136 | 137 | template 138 | class Mem { 139 | public: 140 | typedef T* iterator; 141 | typedef const T* const_iterator; 142 | 143 | Mem() {} 144 | ~Mem() { reset(); } 145 | 146 | Mem(Mem &&other) { 147 | ptr_ = other.ptr_; 148 | size_ = other.size_; 149 | other.ptr_ = nullptr; 150 | other.size_ = 0; 151 | } 152 | 153 | Mem& operator=(Mem &&other) { 154 | reset(); 155 | ptr_ = other.ptr_; 156 | size_ = other.size_; 157 | other.ptr_ = nullptr; 158 | other.size_ = 0; 159 | return *this; 160 | } 161 | 162 | bool valid() const { return ptr_ != nullptr; } 163 | operator bool() const { return valid(); } 164 | 165 | T* alloc(size_t num) { 166 | reset(); 167 | ptr_ = reinterpret_cast(MemoryAlloc(sizeof(T)*num, alignof(T))); 168 | size_ = num; 169 | for(size_t i = 0; i < size_; ++i) { 170 | new (&ptr_[i]) T(); 171 | } 172 | return ptr_; 173 | } 174 | 175 | void reset() { 176 | if (ptr_) { 177 | for(size_t i = 0; i < size_; ++i) { 178 | ptr_[i].~T(); 179 | } 180 | MemoryFree(ptr_); 181 | ptr_ = nullptr; 182 | size_ = 0; 183 | } 184 | } 185 | 186 | void copy(const T* begin, const T* end) { 187 | T *dst = alloc(((size_t)end-(size_t)begin)/sizeof(T)); 188 | for(const T *p = begin; p != end; ++p, ++dst) { 189 | *dst = *p; 190 | } 191 | } 192 | 193 | void copy(ConstMemRef memref) { 194 | if (memref.size() == 0) { 195 | reset(); 196 | } else { 197 | T *dst = alloc(memref.size()); 198 | for(size_t i = 0; i < memref.size(); ++i) { 199 | dst[i] = memref[i]; 200 | } 201 | } 202 | } 203 | 204 | T& operator[](size_t i) { 205 | PX_MEM_ASSERT(i < size_, "Invalid Access"); 206 | return ptr_[i]; 207 | } 208 | 209 | const T& operator[](size_t i) const { 210 | PX_MEM_ASSERT(i < size_, "Invalid Access"); 211 | return ptr_[i]; 212 | } 213 | 214 | iterator begin() const { 215 | return ptr_; 216 | } 217 | iterator end() const { 218 | return ptr_+size_; 219 | } 220 | const_iterator cbegin() const { 221 | return ptr_; 222 | } 223 | const_iterator cend() const { 224 | return ptr_+size_; 225 | } 226 | 227 | size_t size() const { return size_; } 228 | size_t sizeInBytes() const { return size_*sizeof(T); } 229 | 230 | T* get() { return ptr_; } 231 | const T* get() const { return ptr_; } 232 | 233 | operator ConstMemRef() const { return ref(); } 234 | ConstMemRef ref() const { return ConstMemRef(ptr_, size()); } 235 | 236 | private: 237 | T *ptr_ = nullptr; 238 | size_t size_ = 0; 239 | }; 240 | 241 | // minimal standard allocator that uses MemoryAlloc/MemoryFree functions 242 | template 243 | struct Allocator { 244 | typedef T value_type; 245 | Allocator() {} 246 | template Allocator(const Allocator& other) {} 247 | T* allocate(std::size_t n) { 248 | return (T*)MemoryAlloc(sizeof(T)*n, alignof(T)); 249 | } 250 | void deallocate(T* p, std::size_t n) { 251 | MemoryFree(p); 252 | } 253 | }; 254 | template 255 | bool operator==(const Allocator&, const Allocator&) { return false; } 256 | template 257 | bool operator!=(const Allocator&, const Allocator&) { return true; } 258 | 259 | #ifdef PX_MEM_IMPLEMENTATION 260 | namespace { 261 | void* _DefaultMemoryAlloc(size_t mem_size, size_t align) { 262 | size_t mem_plus_align = mem_size+align; 263 | void *raw_mem = std::malloc(mem_plus_align+sizeof(void*)); 264 | void *ptr = ((char*)raw_mem)+sizeof(void*); 265 | void *result = std::align(align, mem_size, ptr , mem_plus_align); 266 | PX_MEM_ASSERT(result != nullptr, "Default Memory Alloc failed"); 267 | ((void**)result)[-1] = raw_mem; 268 | return result; 269 | } 270 | 271 | void _DefaultMemoryFree(void *ptr) { 272 | void *raw_mem = ((void**)ptr)[-1]; 273 | std::free(raw_mem); 274 | } 275 | } 276 | 277 | static struct { 278 | void *(*alloc)(size_t, size_t ) = _DefaultMemoryAlloc; 279 | void (*free)(void*) = _DefaultMemoryFree; 280 | } GLOBAL_mem; 281 | 282 | void *MemoryAlloc(size_t amount, size_t alignment) { 283 | return GLOBAL_mem.alloc(amount, alignment); 284 | } 285 | 286 | void MemoryFree(void *ptr) { 287 | return GLOBAL_mem.free(ptr); 288 | } 289 | 290 | void SetMemoryFunctions( void*(*MemAlloc)(size_t, size_t), void (*MemFree)(void*)) { 291 | if (!MemAlloc && !MemFree) { 292 | MemAlloc = _DefaultMemoryAlloc; 293 | MemFree = _DefaultMemoryFree; 294 | } 295 | GLOBAL_mem.alloc = MemAlloc; 296 | GLOBAL_mem.free = MemFree; 297 | } 298 | #endif 299 | 300 | } // px 301 | 302 | #endif 303 | -------------------------------------------------------------------------------- /examples/deps/sokol_time.h: -------------------------------------------------------------------------------- 1 | #ifndef SOKOL_TIME_INCLUDED 2 | /* 3 | sokol_time.h -- simple cross-platform time measurement 4 | 5 | Project URL: https://github.com/floooh/sokol 6 | 7 | Do this: 8 | #define SOKOL_IMPL 9 | before you include this file in *one* C or C++ file to create the 10 | implementation. 11 | 12 | Optionally provide the following defines with your own implementations: 13 | SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) 14 | SOKOL_API_DECL - public function declaration prefix (default: extern) 15 | SOKOL_API_IMPL - public function implementation prefix (default: -) 16 | 17 | If sokol_time.h is compiled as a DLL, define the following before 18 | including the declaration or implementation: 19 | 20 | SOKOL_DLL 21 | 22 | On Windows, SOKOL_DLL will define SOKOL_API_DECL as __declspec(dllexport) 23 | or __declspec(dllimport) as needed. 24 | 25 | void stm_setup(); 26 | Call once before any other functions to initialize sokol_time 27 | (this calls for instance QueryPerformanceFrequency on Windows) 28 | 29 | uint64_t stm_now(); 30 | Get current point in time in unspecified 'ticks'. The value that 31 | is returned has no relation to the 'wall-clock' time and is 32 | not in a specific time unit, it is only useful to compute 33 | time differences. 34 | 35 | uint64_t stm_diff(uint64_t new, uint64_t old); 36 | Computes the time difference between new and old. This will always 37 | return a positive, non-zero value. 38 | 39 | uint64_t stm_since(uint64_t start); 40 | Takes the current time, and returns the elapsed time since start 41 | (this is a shortcut for "stm_diff(stm_now(), start)") 42 | 43 | uint64_t stm_laptime(uint64_t* last_time); 44 | This is useful for measuring frame time and other recurring 45 | events. It takes the current time, returns the time difference 46 | to the value in last_time, and stores the current time in 47 | last_time for the next call. If the value in last_time is 0, 48 | the return value will be zero (this usually happens on the 49 | very first call). 50 | 51 | uint64_t stm_round_to_common_refresh_rate(uint64_t duration) 52 | This oddly named function takes a measured frame time and 53 | returns the closest "nearby" common display refresh rate frame duration 54 | in ticks. If the input duration isn't close to any common display 55 | refresh rate, the input duration will be returned unchanged as a fallback. 56 | The main purpose of this function is to remove jitter/inaccuracies from 57 | measured frame times, and instead use the display refresh rate as 58 | frame duration. 59 | 60 | Use the following functions to convert a duration in ticks into 61 | useful time units: 62 | 63 | double stm_sec(uint64_t ticks); 64 | double stm_ms(uint64_t ticks); 65 | double stm_us(uint64_t ticks); 66 | double stm_ns(uint64_t ticks); 67 | Converts a tick value into seconds, milliseconds, microseconds 68 | or nanoseconds. Note that not all platforms will have nanosecond 69 | or even microsecond precision. 70 | 71 | Uses the following time measurement functions under the hood: 72 | 73 | Windows: QueryPerformanceFrequency() / QueryPerformanceCounter() 74 | MacOS/iOS: mach_absolute_time() 75 | emscripten: performance.now() 76 | Linux+others: clock_gettime(CLOCK_MONOTONIC) 77 | 78 | zlib/libpng license 79 | 80 | Copyright (c) 2018 Andre Weissflog 81 | 82 | This software is provided 'as-is', without any express or implied warranty. 83 | In no event will the authors be held liable for any damages arising from the 84 | use of this software. 85 | 86 | Permission is granted to anyone to use this software for any purpose, 87 | including commercial applications, and to alter it and redistribute it 88 | freely, subject to the following restrictions: 89 | 90 | 1. The origin of this software must not be misrepresented; you must not 91 | claim that you wrote the original software. If you use this software in a 92 | product, an acknowledgment in the product documentation would be 93 | appreciated but is not required. 94 | 95 | 2. Altered source versions must be plainly marked as such, and must not 96 | be misrepresented as being the original software. 97 | 98 | 3. This notice may not be removed or altered from any source 99 | distribution. 100 | */ 101 | #define SOKOL_TIME_INCLUDED (1) 102 | #include 103 | 104 | #ifndef SOKOL_API_DECL 105 | #if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_IMPL) 106 | #define SOKOL_API_DECL __declspec(dllexport) 107 | #elif defined(_WIN32) && defined(SOKOL_DLL) 108 | #define SOKOL_API_DECL __declspec(dllimport) 109 | #else 110 | #define SOKOL_API_DECL extern 111 | #endif 112 | #endif 113 | 114 | #ifdef __cplusplus 115 | extern "C" { 116 | #endif 117 | 118 | SOKOL_API_DECL void stm_setup(void); 119 | SOKOL_API_DECL uint64_t stm_now(void); 120 | SOKOL_API_DECL uint64_t stm_diff(uint64_t new_ticks, uint64_t old_ticks); 121 | SOKOL_API_DECL uint64_t stm_since(uint64_t start_ticks); 122 | SOKOL_API_DECL uint64_t stm_laptime(uint64_t* last_time); 123 | SOKOL_API_DECL uint64_t stm_round_to_common_refresh_rate(uint64_t frame_ticks); 124 | SOKOL_API_DECL double stm_sec(uint64_t ticks); 125 | SOKOL_API_DECL double stm_ms(uint64_t ticks); 126 | SOKOL_API_DECL double stm_us(uint64_t ticks); 127 | SOKOL_API_DECL double stm_ns(uint64_t ticks); 128 | 129 | #ifdef __cplusplus 130 | } /* extern "C" */ 131 | #endif 132 | #endif // SOKOL_TIME_INCLUDED 133 | 134 | /*-- IMPLEMENTATION ----------------------------------------------------------*/ 135 | #ifdef SOKOL_IMPL 136 | #define SOKOL_TIME_IMPL_INCLUDED (1) 137 | #include /* memset */ 138 | 139 | #ifndef SOKOL_API_IMPL 140 | #define SOKOL_API_IMPL 141 | #endif 142 | #ifndef SOKOL_ASSERT 143 | #include 144 | #define SOKOL_ASSERT(c) assert(c) 145 | #endif 146 | #ifndef _SOKOL_PRIVATE 147 | #if defined(__GNUC__) || defined(__clang__) 148 | #define _SOKOL_PRIVATE __attribute__((unused)) static 149 | #else 150 | #define _SOKOL_PRIVATE static 151 | #endif 152 | #endif 153 | 154 | #if defined(_WIN32) 155 | #ifndef WIN32_LEAN_AND_MEAN 156 | #define WIN32_LEAN_AND_MEAN 157 | #endif 158 | #include 159 | typedef struct { 160 | uint32_t initialized; 161 | LARGE_INTEGER freq; 162 | LARGE_INTEGER start; 163 | } _stm_state_t; 164 | #elif defined(__APPLE__) && defined(__MACH__) 165 | #include 166 | typedef struct { 167 | uint32_t initialized; 168 | mach_timebase_info_data_t timebase; 169 | uint64_t start; 170 | } _stm_state_t; 171 | #elif defined(__EMSCRIPTEN__) 172 | #include 173 | typedef struct { 174 | uint32_t initialized; 175 | double start; 176 | } _stm_state_t; 177 | #else /* anything else, this will need more care for non-Linux platforms */ 178 | #ifdef ESP8266 179 | // On the ESP8266, clock_gettime ignores the first argument and CLOCK_MONOTONIC isn't defined 180 | #define CLOCK_MONOTONIC 0 181 | #endif 182 | #include 183 | typedef struct { 184 | uint32_t initialized; 185 | uint64_t start; 186 | } _stm_state_t; 187 | #endif 188 | static _stm_state_t _stm; 189 | 190 | /* prevent 64-bit overflow when computing relative timestamp 191 | see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3 192 | */ 193 | #if defined(_WIN32) || (defined(__APPLE__) && defined(__MACH__)) 194 | _SOKOL_PRIVATE int64_t int64_muldiv(int64_t value, int64_t numer, int64_t denom) { 195 | int64_t q = value / denom; 196 | int64_t r = value % denom; 197 | return q * numer + r * numer / denom; 198 | } 199 | #endif 200 | 201 | #if defined(__EMSCRIPTEN__) 202 | EM_JS(double, stm_js_perfnow, (void), { 203 | return performance.now(); 204 | }); 205 | #endif 206 | 207 | SOKOL_API_IMPL void stm_setup(void) { 208 | memset(&_stm, 0, sizeof(_stm)); 209 | _stm.initialized = 0xABCDABCD; 210 | #if defined(_WIN32) 211 | QueryPerformanceFrequency(&_stm.freq); 212 | QueryPerformanceCounter(&_stm.start); 213 | #elif defined(__APPLE__) && defined(__MACH__) 214 | mach_timebase_info(&_stm.timebase); 215 | _stm.start = mach_absolute_time(); 216 | #elif defined(__EMSCRIPTEN__) 217 | _stm.start = stm_js_perfnow(); 218 | #else 219 | struct timespec ts; 220 | clock_gettime(CLOCK_MONOTONIC, &ts); 221 | _stm.start = (uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec; 222 | #endif 223 | } 224 | 225 | SOKOL_API_IMPL uint64_t stm_now(void) { 226 | SOKOL_ASSERT(_stm.initialized == 0xABCDABCD); 227 | uint64_t now; 228 | #if defined(_WIN32) 229 | LARGE_INTEGER qpc_t; 230 | QueryPerformanceCounter(&qpc_t); 231 | now = int64_muldiv(qpc_t.QuadPart - _stm.start.QuadPart, 1000000000, _stm.freq.QuadPart); 232 | #elif defined(__APPLE__) && defined(__MACH__) 233 | const uint64_t mach_now = mach_absolute_time() - _stm.start; 234 | now = int64_muldiv(mach_now, _stm.timebase.numer, _stm.timebase.denom); 235 | #elif defined(__EMSCRIPTEN__) 236 | double js_now = stm_js_perfnow() - _stm.start; 237 | SOKOL_ASSERT(js_now >= 0.0); 238 | now = (uint64_t) (js_now * 1000000.0); 239 | #else 240 | struct timespec ts; 241 | clock_gettime(CLOCK_MONOTONIC, &ts); 242 | now = ((uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec) - _stm.start; 243 | #endif 244 | return now; 245 | } 246 | 247 | SOKOL_API_IMPL uint64_t stm_diff(uint64_t new_ticks, uint64_t old_ticks) { 248 | if (new_ticks > old_ticks) { 249 | return new_ticks - old_ticks; 250 | } 251 | else { 252 | return 1; 253 | } 254 | } 255 | 256 | SOKOL_API_IMPL uint64_t stm_since(uint64_t start_ticks) { 257 | return stm_diff(stm_now(), start_ticks); 258 | } 259 | 260 | SOKOL_API_IMPL uint64_t stm_laptime(uint64_t* last_time) { 261 | SOKOL_ASSERT(last_time); 262 | uint64_t dt = 0; 263 | uint64_t now = stm_now(); 264 | if (0 != *last_time) { 265 | dt = stm_diff(now, *last_time); 266 | } 267 | *last_time = now; 268 | return dt; 269 | } 270 | 271 | // first number is frame duration in ns, second number is tolerance in ns, 272 | // the resulting min/max values must not overlap! 273 | static const uint64_t _stm_refresh_rates[][2] = { 274 | { 16666667, 1000000 }, // 60 Hz: 16.6667 +- 1ms 275 | { 13888889, 250000 }, // 72 Hz: 13.8889 +- 0.25ms 276 | { 13333333, 250000 }, // 75 Hz: 13.3333 +- 0.25ms 277 | { 11764706, 250000 }, // 85 Hz: 11.7647 +- 0.25 278 | { 11111111, 250000 }, // 90 Hz: 11.1111 +- 0.25ms 279 | { 8333333, 500000 }, // 120 Hz: 8.3333 +- 0.5ms 280 | { 6944445, 500000 }, // 144 Hz: 6.9445 +- 0.5ms 281 | { 4166667, 1000000 }, // 240 Hz: 4.1666 +- 1ms 282 | { 0, 0 }, // keep the last element always at zero 283 | }; 284 | 285 | SOKOL_API_IMPL uint64_t stm_round_to_common_refresh_rate(uint64_t ticks) { 286 | uint64_t ns; 287 | int i = 0; 288 | while (0 != (ns = _stm_refresh_rates[i][0])) { 289 | uint64_t tol = _stm_refresh_rates[i][1]; 290 | if ((ticks > (ns - tol)) && (ticks < (ns + tol))) { 291 | return ns; 292 | } 293 | i++; 294 | } 295 | // fallthough: didn't fit into any buckets 296 | return ticks; 297 | } 298 | 299 | SOKOL_API_IMPL double stm_sec(uint64_t ticks) { 300 | return (double)ticks / 1000000000.0; 301 | } 302 | 303 | SOKOL_API_IMPL double stm_ms(uint64_t ticks) { 304 | return (double)ticks / 1000000.0; 305 | } 306 | 307 | SOKOL_API_IMPL double stm_us(uint64_t ticks) { 308 | return (double)ticks / 1000.0; 309 | } 310 | 311 | SOKOL_API_IMPL double stm_ns(uint64_t ticks) { 312 | return (double)ticks; 313 | } 314 | #endif /* SOKOL_IMPL */ 315 | 316 | -------------------------------------------------------------------------------- /examples/px_render_example_rtt.cpp: -------------------------------------------------------------------------------- 1 | // Emscripten config 2 | #ifdef __EMSCRIPTEN__ 3 | // Compile with emcc --std=c++14 -s USE_WEBGL2=1 px_render_example_rtt.cpp -o rtt.html 4 | # define SOKOL_GLES3 5 | # define PX_RENDER_BACKEND_GLES 6 | # define GLSL(...) "#version 300 es\n precision highp float;\n" #__VA_ARGS__ 7 | #else 8 | # define PX_RENDER_BACKEND_GL //< Default 9 | # define SOKOL_WIN32_NO_GL_LOADER 10 | # define SOKOL_GLCORE33 11 | #define GLSL(...) "#version 330\n" #__VA_ARGS__ 12 | // OpenGL + px_render 13 | # include "deps/glad.c" 14 | #endif 15 | 16 | // gb_math & sokol_app (Window Support) 17 | #define GB_MATH_IMPLEMENTATION 18 | #define SOKOL_IMPL 19 | #include "deps/gb_math.h" 20 | #include "deps/sokol_app.h" 21 | 22 | #define PX_RENDER_IMPLEMENTATION 23 | #include "../px_render.h" 24 | 25 | using namespace px_render; 26 | 27 | 28 | namespace Cube { 29 | float vertex_data[] = { 30 | -1.0, -1.0, -1.0, 0.1, 0.1, 0.3, 1.0, 0.0, 0.0, 31 | 1.0, -1.0, -1.0, 0.1, 0.1, 0.3, 1.0, 1.0, 0.0, 32 | 1.0, 1.0, -1.0, 0.1, 0.1, 0.3, 1.0, 1.0, 1.0, 33 | -1.0, 1.0, -1.0, 0.1, 0.1, 0.3, 1.0, 0.0, 1.0, 34 | 35 | -1.0, -1.0, 1.0, 0.1, 0.1, 1.0, 1.0, 0.0, 0.0, 36 | 1.0, -1.0, 1.0, 0.1, 0.1, 1.0, 1.0, 1.0, 0.0, 37 | 1.0, 1.0, 1.0, 0.1, 0.1, 1.0, 1.0, 1.0, 1.0, 38 | -1.0, 1.0, 1.0, 0.1, 0.1, 1.0, 1.0, 0.0, 1.0, 39 | 40 | -1.0, -1.0, -1.0, 0.3, 0.1, 0.1, 1.0, 0.0, 0.0, 41 | -1.0, 1.0, -1.0, 0.3, 0.1, 0.1, 1.0, 1.0, 0.0, 42 | -1.0, 1.0, 1.0, 0.3, 0.1, 0.1, 1.0, 1.0, 1.0, 43 | -1.0, -1.0, 1.0, 0.3, 0.1, 0.1, 1.0, 0.0, 1.0, 44 | 45 | 1.0, -1.0, -1.0, 1.0, 0.1, 0.1, 1.0, 0.0, 0.0, 46 | 1.0, 1.0, -1.0, 1.0, 0.1, 0.1, 1.0, 1.0, 0.0, 47 | 1.0, 1.0, 1.0, 1.0, 0.1, 0.1, 1.0, 1.0, 1.0, 48 | 1.0, -1.0, 1.0, 1.0, 0.1, 0.1, 1.0, 0.0, 1.0, 49 | 50 | -1.0, -1.0, -1.0, 0.1, 0.3, 0.1, 1.0, 0.0, 0.0, 51 | -1.0, -1.0, 1.0, 0.1, 0.3, 0.1, 1.0, 1.0, 0.0, 52 | 1.0, -1.0, 1.0, 0.1, 0.3, 0.1, 1.0, 1.0, 1.0, 53 | 1.0, -1.0, -1.0, 0.1, 0.3, 0.1, 1.0, 0.0, 1.0, 54 | 55 | -1.0, 1.0, -1.0, 0.1, 1.0, 0.1, 1.0, 0.0, 0.0, 56 | -1.0, 1.0, 1.0, 0.1, 1.0, 0.1, 1.0, 1.0, 0.0, 57 | 1.0, 1.0, 1.0, 0.1, 1.0, 0.1, 1.0, 1.0, 1.0, 58 | 1.0, 1.0, -1.0, 0.1, 1.0, 0.1, 1.0, 0.0, 1.0 59 | }; 60 | 61 | uint16_t index_data[] = { 62 | 0, 2, 1, 0, 3, 2, 6, 4, 5, 7, 4, 6, 63 | 8, 10, 9, 8, 11, 10, 14, 12, 13, 15, 12, 14, 64 | 16, 18, 17, 16, 19, 18, 22, 20, 21, 23, 20, 22 65 | }; 66 | } 67 | 68 | 69 | namespace Quad { 70 | float vertex_data[] = { 71 | -1.0, -1.0, .0, 0.0, 0.0, 72 | 1.0, -1.0, .0, 1.0, 0.0, 73 | 1.0, 1.0, .0, 1.0, 1.0, 74 | -1.0, 1.0, .0, 0.0, 1.0 75 | }; 76 | uint16_t index_data[] = {0, 2, 1, 0, 3, 2}; 77 | } 78 | 79 | struct { 80 | RenderContext ctx; 81 | Mat4 proj; 82 | Mat4 proj_fb; 83 | Mat4 view; 84 | Mat4 view_fb; 85 | Framebuffer fb; 86 | struct { 87 | Vec3 instance_positions[5000]; 88 | Pipeline material; 89 | Buffer vertex_buff; 90 | Buffer index_buff; 91 | Buffer instance_buff; 92 | Texture texture; 93 | } cube; 94 | struct { 95 | Pipeline material; 96 | Buffer vertex_buff; 97 | Buffer index_buff; 98 | } quad; 99 | } State = {}; 100 | 101 | 102 | void init() { 103 | #ifdef PX_RENDER_BACKEND_GL 104 | gladLoadGL(); 105 | #endif 106 | State.ctx.init(); 107 | gb_mat4_perspective((gbMat4*)&State.proj, gb_to_radians(45.f), 1024/(float)768, 0.05f, 900.0f); 108 | gb_mat4_perspective((gbMat4*)&State.proj_fb, gb_to_radians(45.f), 1.0f, 0.05f, 900.0f); 109 | gb_mat4_look_at((gbMat4*)&State.view, {0.f,0.5f,-3.f}, {0.f,0.f,0.0f}, {0.f,1.f,0.f}); 110 | gb_mat4_look_at((gbMat4*)&State.view_fb, {0.f,10.f,-20.f}, {0.f,0.f,0.0f}, {0.f,1.f,0.f}); 111 | 112 | State.cube.vertex_buff = State.ctx.createBuffer({BufferType::Vertex, sizeof(Cube::vertex_data), Usage::Static}); 113 | State.cube.index_buff = State.ctx.createBuffer({BufferType::Index, sizeof(Cube::index_data), Usage::Static}); 114 | State.cube.instance_buff = State.ctx.createBuffer({BufferType::Vertex, sizeof(State.cube.instance_positions), Usage::Stream}); 115 | 116 | State.quad.vertex_buff = State.ctx.createBuffer({BufferType::Vertex, sizeof(Quad::vertex_data), Usage::Static}); 117 | State.quad.index_buff = State.ctx.createBuffer({BufferType::Index, sizeof(Quad::index_data), Usage::Static}); 118 | 119 | { // Cube Material & Object setup 120 | Pipeline::Info pipeline_info; 121 | pipeline_info.shader.vertex = GLSL( 122 | uniform mat4 u_modelViewProjection; 123 | in vec3 position; 124 | in vec3 instance_position; 125 | in vec4 color; 126 | in vec2 uv; 127 | out vec4 v_color; 128 | out vec2 v_uv; 129 | void main() { 130 | gl_Position = u_modelViewProjection * vec4(position + instance_position,1.0); 131 | v_color = color; 132 | v_uv = uv; 133 | } 134 | ); 135 | pipeline_info.shader.fragment = GLSL( 136 | in vec4 v_color; 137 | in vec2 v_uv; 138 | uniform sampler2D u_tex0; 139 | out vec4 color_out; 140 | void main() { 141 | color_out = vec4(v_color.rgb*texture(u_tex0, v_uv).r,1.0); 142 | } 143 | ); 144 | pipeline_info.attribs[0] = {"position", VertexFormat::Float3}; 145 | pipeline_info.attribs[1] = {"color", VertexFormat::Float4}; 146 | pipeline_info.attribs[2] = {"uv", VertexFormat::Float2}; 147 | pipeline_info.attribs[3] = {"instance_position", VertexFormat::Float3, 1, VertexStep::PerInstance}; 148 | pipeline_info.textures[0] = TextureType::T2D; 149 | State.cube.material = State.ctx.createPipeline(pipeline_info); 150 | 151 | 152 | Texture::Info tex_info; 153 | tex_info.format = TexelsFormat::R_U8; 154 | tex_info.width = 4; 155 | tex_info.height = 4; 156 | tex_info.magnification_filter = SamplerFiltering::Nearest; 157 | tex_info.minification_filter = SamplerFiltering::Nearest; 158 | State.cube.texture = State.ctx.createTexture(tex_info); 159 | { 160 | DisplayList dl; 161 | dl.fillBufferCommand() 162 | .set_buffer(State.cube.vertex_buff) 163 | .set_data(Cube::vertex_data) 164 | .set_size(sizeof(Cube::vertex_data)); 165 | dl.fillBufferCommand() 166 | .set_buffer(State.cube.index_buff) 167 | .set_data(Cube::index_data) 168 | .set_size(sizeof(Cube::index_data)); 169 | 170 | uint8_t tex_data[16] = { 171 | 255, 0, 255, 0, 172 | 0, 255, 0, 255, 173 | 255, 0, 255, 0, 174 | 0, 255, 0, 255, 175 | }; 176 | dl.fillTextureCommand() 177 | .set_texture(State.cube.texture) 178 | .set_data(tex_data) 179 | ; 180 | State.ctx.submitDisplayList(std::move(dl)); 181 | } 182 | } // Cube Setup 183 | 184 | 185 | { // Quad Material & Object setup 186 | Pipeline::Info pipeline_info; 187 | pipeline_info.shader.vertex = GLSL( 188 | uniform mat4 u_modelViewProjection; 189 | in vec3 position; 190 | in vec4 color; 191 | in vec2 uv; 192 | out vec2 v_uv; 193 | void main() { 194 | gl_Position = u_modelViewProjection * vec4(position,1.0); 195 | v_uv = uv; 196 | } 197 | ); 198 | pipeline_info.shader.fragment = GLSL( 199 | in vec2 v_uv; 200 | uniform sampler2D u_tex0; 201 | out vec4 color_out; 202 | void main() { 203 | color_out = texture(u_tex0, v_uv); 204 | } 205 | ); 206 | pipeline_info.attribs[0] = {"position", VertexFormat::Float3}; 207 | pipeline_info.attribs[1] = {"uv", VertexFormat::Float2}; 208 | pipeline_info.textures[0] = TextureType::T2D; 209 | pipeline_info.cull = Cull::Disabled; 210 | State.quad.material = State.ctx.createPipeline(pipeline_info); 211 | DisplayList dl; 212 | dl.fillBufferCommand() 213 | .set_buffer(State.quad.vertex_buff) 214 | .set_data(Quad::vertex_data) 215 | .set_size(sizeof(Quad::vertex_data)); 216 | dl.fillBufferCommand() 217 | .set_buffer(State.quad.index_buff) 218 | .set_data(Quad::index_data) 219 | .set_size(sizeof(Quad::index_data)); 220 | State.ctx.submitDisplayList(std::move(dl)); 221 | } 222 | 223 | // Create Framebuffer (offscreen rendering) 224 | Texture::Info fb_tex_color = {640,640}; 225 | Texture::Info fb_tex_depth = {640,640}; 226 | fb_tex_color.format = TexelsFormat::RGBA_U8; 227 | fb_tex_depth.format = TexelsFormat::Depth_U16; 228 | 229 | State.fb = State.ctx.createFramebuffer({fb_tex_color, fb_tex_depth}); 230 | } 231 | 232 | void frame() { 233 | // Render could be done on other thread, but for simplicity and 234 | // to make the example work along with EMSCRIPTEN let's pretend 235 | static float v = 0; 236 | for (int i = 0; i < sizeof(State.cube.instance_positions) / sizeof(Vec3); ++i) { 237 | State.cube.instance_positions[i] = { 238 | (float)(i%1000)*3.0f, 239 | 5.0f*(float)(gb_sin(i*GB_MATH_PI/10+v)), 240 | (i/1000)*3.0f}; 241 | } 242 | Mat4 model; 243 | gb_mat4_rotate((gbMat4*)model.f, {0,1,0}, v); 244 | v+= 0.01; 245 | DisplayList dl; 246 | dl.setupViewCommand() 247 | .set_viewport({0,0,640,640}) 248 | .set_projection_matrix(State.proj_fb) 249 | .set_view_matrix(State.view_fb) 250 | .set_framebuffer(State.fb) 251 | ; 252 | dl.clearCommand() 253 | .set_color({0.2f,0.2f,0.2f,1.0f}) 254 | .set_clear_color(true) 255 | .set_clear_depth(true) 256 | ; 257 | dl.fillBufferCommand() 258 | .set_buffer(State.cube.instance_buff) 259 | .set_data(State.cube.instance_positions) 260 | .set_size(sizeof(State.cube.instance_positions)) 261 | ; 262 | dl.setupPipelineCommand() 263 | .set_pipeline(State.cube.material) 264 | .set_buffer(0,State.cube.vertex_buff) 265 | .set_buffer(1,State.cube.instance_buff) 266 | .set_model_matrix(model) 267 | .set_texture(0, State.cube.texture) 268 | ; 269 | dl.renderCommand() 270 | .set_index_buffer(State.cube.index_buff) 271 | .set_count(sizeof(Cube::index_data)/sizeof(uint16_t)) 272 | .set_type(IndexFormat::UInt16) 273 | .set_instances(sizeof(State.cube.instance_positions)/sizeof(State.cube.instance_positions[0])) 274 | ; 275 | // now render to the main FrameBuffer... 276 | dl.setupViewCommand() 277 | .set_viewport({0,0,1024,768}) 278 | .set_projection_matrix(State.proj) 279 | .set_view_matrix(State.view) 280 | ; 281 | dl.clearCommand() 282 | .set_color({0.5f,0.7f,0.8f,1.0f}) 283 | .set_clear_color(true) 284 | .set_clear_depth(true) 285 | ; 286 | 287 | gb_mat4_rotate((gbMat4*)model.f, {0,1,0}, v*0.25); 288 | dl.setupPipelineCommand() 289 | .set_pipeline(State.quad.material) 290 | .set_buffer(0,State.quad.vertex_buff) 291 | .set_texture(0, State.fb.color_texture()) 292 | .set_model_matrix(model) 293 | ; 294 | dl.renderCommand() 295 | .set_index_buffer(State.quad.index_buff) 296 | .set_count(sizeof(Quad::index_data)/sizeof(uint16_t)) 297 | .set_type(IndexFormat::UInt16) 298 | ; 299 | 300 | State.ctx.submitDisplayListAndSwap(std::move(dl)); 301 | 302 | // GPU Render... 303 | while(true) { 304 | RenderContext::Result::Enum result = State.ctx.executeOnGPU(); 305 | if (result != RenderContext::Result::OK) break; 306 | } 307 | } 308 | 309 | void cleanup() { 310 | State.ctx.finish(); 311 | } 312 | 313 | sapp_desc sokol_main(int argc, char **argv) { 314 | sapp_desc d = {}; 315 | d.init_cb = init; 316 | d.frame_cb = frame; 317 | d.cleanup_cb = cleanup; 318 | d.width = 1024; 319 | d.height = 768; 320 | d.window_title = "PX-Render Test"; 321 | return d; 322 | } 323 | 324 | -------------------------------------------------------------------------------- /examples/deps/imstb_rectpack.h: -------------------------------------------------------------------------------- 1 | // [DEAR IMGUI] 2 | // This is a slightly modified version of stb_rect_pack.h 1.00. 3 | // Those changes would need to be pushed into nothings/stb: 4 | // - Added STBRP__CDECL 5 | // Grep for [DEAR IMGUI] to find the changes. 6 | 7 | // stb_rect_pack.h - v1.00 - public domain - rectangle packing 8 | // Sean Barrett 2014 9 | // 10 | // Useful for e.g. packing rectangular textures into an atlas. 11 | // Does not do rotation. 12 | // 13 | // Not necessarily the awesomest packing method, but better than 14 | // the totally naive one in stb_truetype (which is primarily what 15 | // this is meant to replace). 16 | // 17 | // Has only had a few tests run, may have issues. 18 | // 19 | // More docs to come. 20 | // 21 | // No memory allocations; uses qsort() and assert() from stdlib. 22 | // Can override those by defining STBRP_SORT and STBRP_ASSERT. 23 | // 24 | // This library currently uses the Skyline Bottom-Left algorithm. 25 | // 26 | // Please note: better rectangle packers are welcome! Please 27 | // implement them to the same API, but with a different init 28 | // function. 29 | // 30 | // Credits 31 | // 32 | // Library 33 | // Sean Barrett 34 | // Minor features 35 | // Martins Mozeiko 36 | // github:IntellectualKitty 37 | // 38 | // Bugfixes / warning fixes 39 | // Jeremy Jaussaud 40 | // Fabian Giesen 41 | // 42 | // Version history: 43 | // 44 | // 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles 45 | // 0.99 (2019-02-07) warning fixes 46 | // 0.11 (2017-03-03) return packing success/fail result 47 | // 0.10 (2016-10-25) remove cast-away-const to avoid warnings 48 | // 0.09 (2016-08-27) fix compiler warnings 49 | // 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0) 50 | // 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0) 51 | // 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort 52 | // 0.05: added STBRP_ASSERT to allow replacing assert 53 | // 0.04: fixed minor bug in STBRP_LARGE_RECTS support 54 | // 0.01: initial release 55 | // 56 | // LICENSE 57 | // 58 | // See end of file for license information. 59 | 60 | ////////////////////////////////////////////////////////////////////////////// 61 | // 62 | // INCLUDE SECTION 63 | // 64 | 65 | #ifndef STB_INCLUDE_STB_RECT_PACK_H 66 | #define STB_INCLUDE_STB_RECT_PACK_H 67 | 68 | #define STB_RECT_PACK_VERSION 1 69 | 70 | #ifdef STBRP_STATIC 71 | #define STBRP_DEF static 72 | #else 73 | #define STBRP_DEF extern 74 | #endif 75 | 76 | #ifdef __cplusplus 77 | extern "C" { 78 | #endif 79 | 80 | typedef struct stbrp_context stbrp_context; 81 | typedef struct stbrp_node stbrp_node; 82 | typedef struct stbrp_rect stbrp_rect; 83 | 84 | #ifdef STBRP_LARGE_RECTS 85 | typedef int stbrp_coord; 86 | #else 87 | typedef unsigned short stbrp_coord; 88 | #endif 89 | 90 | STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); 91 | // Assign packed locations to rectangles. The rectangles are of type 92 | // 'stbrp_rect' defined below, stored in the array 'rects', and there 93 | // are 'num_rects' many of them. 94 | // 95 | // Rectangles which are successfully packed have the 'was_packed' flag 96 | // set to a non-zero value and 'x' and 'y' store the minimum location 97 | // on each axis (i.e. bottom-left in cartesian coordinates, top-left 98 | // if you imagine y increasing downwards). Rectangles which do not fit 99 | // have the 'was_packed' flag set to 0. 100 | // 101 | // You should not try to access the 'rects' array from another thread 102 | // while this function is running, as the function temporarily reorders 103 | // the array while it executes. 104 | // 105 | // To pack into another rectangle, you need to call stbrp_init_target 106 | // again. To continue packing into the same rectangle, you can call 107 | // this function again. Calling this multiple times with multiple rect 108 | // arrays will probably produce worse packing results than calling it 109 | // a single time with the full rectangle array, but the option is 110 | // available. 111 | // 112 | // The function returns 1 if all of the rectangles were successfully 113 | // packed and 0 otherwise. 114 | 115 | struct stbrp_rect 116 | { 117 | // reserved for your use: 118 | int id; 119 | 120 | // input: 121 | stbrp_coord w, h; 122 | 123 | // output: 124 | stbrp_coord x, y; 125 | int was_packed; // non-zero if valid packing 126 | 127 | }; // 16 bytes, nominally 128 | 129 | 130 | STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); 131 | // Initialize a rectangle packer to: 132 | // pack a rectangle that is 'width' by 'height' in dimensions 133 | // using temporary storage provided by the array 'nodes', which is 'num_nodes' long 134 | // 135 | // You must call this function every time you start packing into a new target. 136 | // 137 | // There is no "shutdown" function. The 'nodes' memory must stay valid for 138 | // the following stbrp_pack_rects() call (or calls), but can be freed after 139 | // the call (or calls) finish. 140 | // 141 | // Note: to guarantee best results, either: 142 | // 1. make sure 'num_nodes' >= 'width' 143 | // or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' 144 | // 145 | // If you don't do either of the above things, widths will be quantized to multiples 146 | // of small integers to guarantee the algorithm doesn't run out of temporary storage. 147 | // 148 | // If you do #2, then the non-quantized algorithm will be used, but the algorithm 149 | // may run out of temporary storage and be unable to pack some rectangles. 150 | 151 | STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); 152 | // Optionally call this function after init but before doing any packing to 153 | // change the handling of the out-of-temp-memory scenario, described above. 154 | // If you call init again, this will be reset to the default (false). 155 | 156 | 157 | STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); 158 | // Optionally select which packing heuristic the library should use. Different 159 | // heuristics will produce better/worse results for different data sets. 160 | // If you call init again, this will be reset to the default. 161 | 162 | enum 163 | { 164 | STBRP_HEURISTIC_Skyline_default=0, 165 | STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, 166 | STBRP_HEURISTIC_Skyline_BF_sortHeight 167 | }; 168 | 169 | 170 | ////////////////////////////////////////////////////////////////////////////// 171 | // 172 | // the details of the following structures don't matter to you, but they must 173 | // be visible so you can handle the memory allocations for them 174 | 175 | struct stbrp_node 176 | { 177 | stbrp_coord x,y; 178 | stbrp_node *next; 179 | }; 180 | 181 | struct stbrp_context 182 | { 183 | int width; 184 | int height; 185 | int align; 186 | int init_mode; 187 | int heuristic; 188 | int num_nodes; 189 | stbrp_node *active_head; 190 | stbrp_node *free_head; 191 | stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' 192 | }; 193 | 194 | #ifdef __cplusplus 195 | } 196 | #endif 197 | 198 | #endif 199 | 200 | ////////////////////////////////////////////////////////////////////////////// 201 | // 202 | // IMPLEMENTATION SECTION 203 | // 204 | 205 | #ifdef STB_RECT_PACK_IMPLEMENTATION 206 | #ifndef STBRP_SORT 207 | #include 208 | #define STBRP_SORT qsort 209 | #endif 210 | 211 | #ifndef STBRP_ASSERT 212 | #include 213 | #define STBRP_ASSERT assert 214 | #endif 215 | 216 | // [DEAR IMGUI] Added STBRP__CDECL 217 | #ifdef _MSC_VER 218 | #define STBRP__NOTUSED(v) (void)(v) 219 | #define STBRP__CDECL __cdecl 220 | #else 221 | #define STBRP__NOTUSED(v) (void)sizeof(v) 222 | #define STBRP__CDECL 223 | #endif 224 | 225 | enum 226 | { 227 | STBRP__INIT_skyline = 1 228 | }; 229 | 230 | STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) 231 | { 232 | switch (context->init_mode) { 233 | case STBRP__INIT_skyline: 234 | STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); 235 | context->heuristic = heuristic; 236 | break; 237 | default: 238 | STBRP_ASSERT(0); 239 | } 240 | } 241 | 242 | STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) 243 | { 244 | if (allow_out_of_mem) 245 | // if it's ok to run out of memory, then don't bother aligning them; 246 | // this gives better packing, but may fail due to OOM (even though 247 | // the rectangles easily fit). @TODO a smarter approach would be to only 248 | // quantize once we've hit OOM, then we could get rid of this parameter. 249 | context->align = 1; 250 | else { 251 | // if it's not ok to run out of memory, then quantize the widths 252 | // so that num_nodes is always enough nodes. 253 | // 254 | // I.e. num_nodes * align >= width 255 | // align >= width / num_nodes 256 | // align = ceil(width/num_nodes) 257 | 258 | context->align = (context->width + context->num_nodes-1) / context->num_nodes; 259 | } 260 | } 261 | 262 | STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) 263 | { 264 | int i; 265 | #ifndef STBRP_LARGE_RECTS 266 | STBRP_ASSERT(width <= 0xffff && height <= 0xffff); 267 | #endif 268 | 269 | for (i=0; i < num_nodes-1; ++i) 270 | nodes[i].next = &nodes[i+1]; 271 | nodes[i].next = NULL; 272 | context->init_mode = STBRP__INIT_skyline; 273 | context->heuristic = STBRP_HEURISTIC_Skyline_default; 274 | context->free_head = &nodes[0]; 275 | context->active_head = &context->extra[0]; 276 | context->width = width; 277 | context->height = height; 278 | context->num_nodes = num_nodes; 279 | stbrp_setup_allow_out_of_mem(context, 0); 280 | 281 | // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) 282 | context->extra[0].x = 0; 283 | context->extra[0].y = 0; 284 | context->extra[0].next = &context->extra[1]; 285 | context->extra[1].x = (stbrp_coord) width; 286 | #ifdef STBRP_LARGE_RECTS 287 | context->extra[1].y = (1<<30); 288 | #else 289 | context->extra[1].y = 65535; 290 | #endif 291 | context->extra[1].next = NULL; 292 | } 293 | 294 | // find minimum y position if it starts at x1 295 | static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) 296 | { 297 | stbrp_node *node = first; 298 | int x1 = x0 + width; 299 | int min_y, visited_width, waste_area; 300 | 301 | STBRP__NOTUSED(c); 302 | 303 | STBRP_ASSERT(first->x <= x0); 304 | 305 | #if 0 306 | // skip in case we're past the node 307 | while (node->next->x <= x0) 308 | ++node; 309 | #else 310 | STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency 311 | #endif 312 | 313 | STBRP_ASSERT(node->x <= x0); 314 | 315 | min_y = 0; 316 | waste_area = 0; 317 | visited_width = 0; 318 | while (node->x < x1) { 319 | if (node->y > min_y) { 320 | // raise min_y higher. 321 | // we've accounted for all waste up to min_y, 322 | // but we'll now add more waste for everything we've visted 323 | waste_area += visited_width * (node->y - min_y); 324 | min_y = node->y; 325 | // the first time through, visited_width might be reduced 326 | if (node->x < x0) 327 | visited_width += node->next->x - x0; 328 | else 329 | visited_width += node->next->x - node->x; 330 | } else { 331 | // add waste area 332 | int under_width = node->next->x - node->x; 333 | if (under_width + visited_width > width) 334 | under_width = width - visited_width; 335 | waste_area += under_width * (min_y - node->y); 336 | visited_width += under_width; 337 | } 338 | node = node->next; 339 | } 340 | 341 | *pwaste = waste_area; 342 | return min_y; 343 | } 344 | 345 | typedef struct 346 | { 347 | int x,y; 348 | stbrp_node **prev_link; 349 | } stbrp__findresult; 350 | 351 | static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) 352 | { 353 | int best_waste = (1<<30), best_x, best_y = (1 << 30); 354 | stbrp__findresult fr; 355 | stbrp_node **prev, *node, *tail, **best = NULL; 356 | 357 | // align to multiple of c->align 358 | width = (width + c->align - 1); 359 | width -= width % c->align; 360 | STBRP_ASSERT(width % c->align == 0); 361 | 362 | // if it can't possibly fit, bail immediately 363 | if (width > c->width || height > c->height) { 364 | fr.prev_link = NULL; 365 | fr.x = fr.y = 0; 366 | return fr; 367 | } 368 | 369 | node = c->active_head; 370 | prev = &c->active_head; 371 | while (node->x + width <= c->width) { 372 | int y,waste; 373 | y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); 374 | if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL 375 | // bottom left 376 | if (y < best_y) { 377 | best_y = y; 378 | best = prev; 379 | } 380 | } else { 381 | // best-fit 382 | if (y + height <= c->height) { 383 | // can only use it if it first vertically 384 | if (y < best_y || (y == best_y && waste < best_waste)) { 385 | best_y = y; 386 | best_waste = waste; 387 | best = prev; 388 | } 389 | } 390 | } 391 | prev = &node->next; 392 | node = node->next; 393 | } 394 | 395 | best_x = (best == NULL) ? 0 : (*best)->x; 396 | 397 | // if doing best-fit (BF), we also have to try aligning right edge to each node position 398 | // 399 | // e.g, if fitting 400 | // 401 | // ____________________ 402 | // |____________________| 403 | // 404 | // into 405 | // 406 | // | | 407 | // | ____________| 408 | // |____________| 409 | // 410 | // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned 411 | // 412 | // This makes BF take about 2x the time 413 | 414 | if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { 415 | tail = c->active_head; 416 | node = c->active_head; 417 | prev = &c->active_head; 418 | // find first node that's admissible 419 | while (tail->x < width) 420 | tail = tail->next; 421 | while (tail) { 422 | int xpos = tail->x - width; 423 | int y,waste; 424 | STBRP_ASSERT(xpos >= 0); 425 | // find the left position that matches this 426 | while (node->next->x <= xpos) { 427 | prev = &node->next; 428 | node = node->next; 429 | } 430 | STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); 431 | y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); 432 | if (y + height <= c->height) { 433 | if (y <= best_y) { 434 | if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { 435 | best_x = xpos; 436 | STBRP_ASSERT(y <= best_y); 437 | best_y = y; 438 | best_waste = waste; 439 | best = prev; 440 | } 441 | } 442 | } 443 | tail = tail->next; 444 | } 445 | } 446 | 447 | fr.prev_link = best; 448 | fr.x = best_x; 449 | fr.y = best_y; 450 | return fr; 451 | } 452 | 453 | static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) 454 | { 455 | // find best position according to heuristic 456 | stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); 457 | stbrp_node *node, *cur; 458 | 459 | // bail if: 460 | // 1. it failed 461 | // 2. the best node doesn't fit (we don't always check this) 462 | // 3. we're out of memory 463 | if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { 464 | res.prev_link = NULL; 465 | return res; 466 | } 467 | 468 | // on success, create new node 469 | node = context->free_head; 470 | node->x = (stbrp_coord) res.x; 471 | node->y = (stbrp_coord) (res.y + height); 472 | 473 | context->free_head = node->next; 474 | 475 | // insert the new node into the right starting point, and 476 | // let 'cur' point to the remaining nodes needing to be 477 | // stiched back in 478 | 479 | cur = *res.prev_link; 480 | if (cur->x < res.x) { 481 | // preserve the existing one, so start testing with the next one 482 | stbrp_node *next = cur->next; 483 | cur->next = node; 484 | cur = next; 485 | } else { 486 | *res.prev_link = node; 487 | } 488 | 489 | // from here, traverse cur and free the nodes, until we get to one 490 | // that shouldn't be freed 491 | while (cur->next && cur->next->x <= res.x + width) { 492 | stbrp_node *next = cur->next; 493 | // move the current node to the free list 494 | cur->next = context->free_head; 495 | context->free_head = cur; 496 | cur = next; 497 | } 498 | 499 | // stitch the list back in 500 | node->next = cur; 501 | 502 | if (cur->x < res.x + width) 503 | cur->x = (stbrp_coord) (res.x + width); 504 | 505 | #ifdef _DEBUG 506 | cur = context->active_head; 507 | while (cur->x < context->width) { 508 | STBRP_ASSERT(cur->x < cur->next->x); 509 | cur = cur->next; 510 | } 511 | STBRP_ASSERT(cur->next == NULL); 512 | 513 | { 514 | int count=0; 515 | cur = context->active_head; 516 | while (cur) { 517 | cur = cur->next; 518 | ++count; 519 | } 520 | cur = context->free_head; 521 | while (cur) { 522 | cur = cur->next; 523 | ++count; 524 | } 525 | STBRP_ASSERT(count == context->num_nodes+2); 526 | } 527 | #endif 528 | 529 | return res; 530 | } 531 | 532 | // [DEAR IMGUI] Added STBRP__CDECL 533 | static int STBRP__CDECL rect_height_compare(const void *a, const void *b) 534 | { 535 | const stbrp_rect *p = (const stbrp_rect *) a; 536 | const stbrp_rect *q = (const stbrp_rect *) b; 537 | if (p->h > q->h) 538 | return -1; 539 | if (p->h < q->h) 540 | return 1; 541 | return (p->w > q->w) ? -1 : (p->w < q->w); 542 | } 543 | 544 | // [DEAR IMGUI] Added STBRP__CDECL 545 | static int STBRP__CDECL rect_original_order(const void *a, const void *b) 546 | { 547 | const stbrp_rect *p = (const stbrp_rect *) a; 548 | const stbrp_rect *q = (const stbrp_rect *) b; 549 | return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); 550 | } 551 | 552 | #ifdef STBRP_LARGE_RECTS 553 | #define STBRP__MAXVAL 0xffffffff 554 | #else 555 | #define STBRP__MAXVAL 0xffff 556 | #endif 557 | 558 | STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) 559 | { 560 | int i, all_rects_packed = 1; 561 | 562 | // we use the 'was_packed' field internally to allow sorting/unsorting 563 | for (i=0; i < num_rects; ++i) { 564 | rects[i].was_packed = i; 565 | } 566 | 567 | // sort according to heuristic 568 | STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); 569 | 570 | for (i=0; i < num_rects; ++i) { 571 | if (rects[i].w == 0 || rects[i].h == 0) { 572 | rects[i].x = rects[i].y = 0; // empty rect needs no space 573 | } else { 574 | stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); 575 | if (fr.prev_link) { 576 | rects[i].x = (stbrp_coord) fr.x; 577 | rects[i].y = (stbrp_coord) fr.y; 578 | } else { 579 | rects[i].x = rects[i].y = STBRP__MAXVAL; 580 | } 581 | } 582 | } 583 | 584 | // unsort 585 | STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); 586 | 587 | // set was_packed flags and all_rects_packed status 588 | for (i=0; i < num_rects; ++i) { 589 | rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); 590 | if (!rects[i].was_packed) 591 | all_rects_packed = 0; 592 | } 593 | 594 | // return the all_rects_packed status 595 | return all_rects_packed; 596 | } 597 | #endif 598 | 599 | /* 600 | ------------------------------------------------------------------------------ 601 | This software is available under 2 licenses -- choose whichever you prefer. 602 | ------------------------------------------------------------------------------ 603 | ALTERNATIVE A - MIT License 604 | Copyright (c) 2017 Sean Barrett 605 | Permission is hereby granted, free of charge, to any person obtaining a copy of 606 | this software and associated documentation files (the "Software"), to deal in 607 | the Software without restriction, including without limitation the rights to 608 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 609 | of the Software, and to permit persons to whom the Software is furnished to do 610 | so, subject to the following conditions: 611 | The above copyright notice and this permission notice shall be included in all 612 | copies or substantial portions of the Software. 613 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 614 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 615 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 616 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 617 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 618 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 619 | SOFTWARE. 620 | ------------------------------------------------------------------------------ 621 | ALTERNATIVE B - Public Domain (www.unlicense.org) 622 | This is free and unencumbered software released into the public domain. 623 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 624 | software, either in source code form or as a compiled binary, for any purpose, 625 | commercial or non-commercial, and by any means. 626 | In jurisdictions that recognize copyright laws, the author or authors of this 627 | software dedicate any and all copyright interest in the software to the public 628 | domain. We make this dedication for the benefit of the public at large and to 629 | the detriment of our heirs and successors. We intend this dedication to be an 630 | overt act of relinquishment in perpetuity of all present and future rights to 631 | this software under copyright law. 632 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 633 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 634 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 635 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 636 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 637 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 638 | ------------------------------------------------------------------------------ 639 | */ 640 | -------------------------------------------------------------------------------- /px_render_gltf.h: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright (c) 2018 Jose L. Hidalgo (PpluX) 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | ----------------------------------------------------------------------------- */ 21 | 22 | // USAGE 23 | // 24 | // In *ONE* C++ file you need to declare 25 | // #define PX_RENDER_GLTF_IMPLEMENTATION 26 | // before including the file that contains px_render_gltf.h 27 | // 28 | // px_render_gltf must be included *AFTER* px_render and tiny_gltf.h (when expanding 29 | // the implementation) 30 | 31 | #ifndef PX_RENDER_GLTF 32 | #define PX_RENDER_GLTF 33 | 34 | #include 35 | 36 | #ifndef PX_RENDER 37 | #error px_render must be included before px_render_gltf (because gltf plugin does not include px_render.h) 38 | #endif 39 | 40 | namespace px_render { 41 | 42 | struct GLTF { 43 | struct Flags { 44 | enum Enum { 45 | Geometry_Position = 1<<0, // Vec3f 46 | Geometry_Normal = 1<<1, // Vec3f 47 | Geometry_TexCoord0 = 1<<2, // Vec2f 48 | Geometry_Tangent = 1<<3, // Vec3f 49 | Material = 1<<10, 50 | ComputeBounds = 1<<11, 51 | All = 0xFFFFFFFF 52 | }; 53 | }; 54 | 55 | void init(RenderContext *_ctx, const tinygltf::Model &_model, uint32_t import_flags = Flags::All); 56 | void freeResources(); 57 | ~GLTF() { freeResources(); } 58 | 59 | struct Primitive { 60 | uint32_t node = 0; 61 | uint32_t mesh = 0; 62 | uint32_t index_offset = 0; // in uint32_t units 63 | uint32_t index_count = 0; 64 | int32_t material = -1; 65 | Vec3 bounds_min = {0.0f, 0.0f, 0.0f}; // bounds in world coordinates 66 | Vec3 bounds_max = {0.0f, 0.0f, 0.0f}; // bounds in world coordinates 67 | }; 68 | 69 | struct Node { 70 | Mat4 transform; // transformation relative to its parent 71 | Mat4 model; // model->world transform 72 | uint32_t parent = 0; 73 | }; 74 | 75 | struct Material { 76 | std::string name; 77 | struct { 78 | int32_t texture = -1; 79 | Vec4 factor = {1.0f, 1.0f, 1.0f, 1.0f}; 80 | } base_color; 81 | struct { 82 | int32_t texture = -1; 83 | float metallic_factor = 0.5f; 84 | float roughness_factor = 0.5f; 85 | } metallic_roughness; 86 | struct { 87 | int32_t texture = -1; 88 | float factor = 1.0f; // normal-scale 89 | } normal; 90 | struct { 91 | int32_t texture = -1; 92 | float factor = 1.0f; // occlusion-strength 93 | } occlusion; 94 | struct { 95 | int32_t texture = -1; 96 | Vec3 factor = {1.0f, 1.0f, 1.0f}; 97 | } emmisive; 98 | }; 99 | 100 | struct Texture { 101 | std::string uri; 102 | px_render::Texture::Info info; 103 | px_render::Texture tex; 104 | }; 105 | 106 | RenderContext *ctx = nullptr; 107 | Buffer vertex_buffer; 108 | Buffer index_buffer; 109 | uint32_t num_primitives = 0; 110 | uint32_t num_nodes = 0; 111 | uint32_t num_materials = 0; 112 | uint32_t num_textures = 0; 113 | std::unique_ptr nodes; 114 | std::unique_ptr primitives; 115 | std::unique_ptr textures; 116 | std::unique_ptr materials; 117 | Vec3 bounds_min = {0.0f, 0.0f, 0.0f}; // bounds in world coordinates 118 | Vec3 bounds_max = {0.0f, 0.0f, 0.0f}; // bounds in world coordinates 119 | }; 120 | 121 | } 122 | 123 | #endif //PX_RENDER_GLTF 124 | 125 | 126 | //---------------------------------------------------------------------------- 127 | #if defined(PX_RENDER_GLTF_IMPLEMENTATION) && !defined(PX_RENDER_GLTF_IMPLEMENTATION_DONE) 128 | 129 | #define PX_RENDER_GTLF_IMPLEMENTATION_DONE 130 | #include 131 | 132 | #ifndef TINY_GLTF_H_ 133 | #error tiny_gltf must be included before px_render_gltf (because gltf plugin does not include tiny_gltf.h) 134 | #endif // PX_RENDER_GLTF_IMPLEMENTATION 135 | 136 | namespace px_render { 137 | 138 | namespace GLTF_Imp { 139 | 140 | using NodeTraverseFunc = std::function; 141 | using IndexTraverseFunc = std::function; 142 | 143 | static void NodeTraverse( 144 | const tinygltf::Model &model, 145 | uint32_t raw_node_pos, 146 | uint32_t raw_parent_node_pos, 147 | NodeTraverseFunc func) { 148 | func(model, raw_node_pos, raw_parent_node_pos); 149 | const tinygltf::Node &node = model.nodes[raw_node_pos]; 150 | for (uint32_t i = 0; i < node.children.size(); ++i) { 151 | NodeTraverse(model, node.children[i], raw_node_pos, func); 152 | } 153 | } 154 | 155 | static const uint32_t InvalidNode = (uint32_t)-1; 156 | 157 | static void NodeTraverse(const tinygltf::Model &model, NodeTraverseFunc func) { 158 | int scene_index = std::max(model.defaultScene, 0); 159 | const tinygltf::Scene &scene = model.scenes[scene_index]; 160 | for (uint32_t i = 0; i < scene.nodes.size(); ++i) { 161 | NodeTraverse(model, scene.nodes[i], InvalidNode, func); 162 | } 163 | } 164 | 165 | static void IndexTraverse(const tinygltf::Model &model, const tinygltf::Accessor &index_accesor, IndexTraverseFunc func) { 166 | const tinygltf::BufferView &buffer_view = model.bufferViews[index_accesor.bufferView]; 167 | const tinygltf::Buffer &buffer = model.buffers[buffer_view.buffer]; 168 | const uint8_t* base = &buffer.data.at(buffer_view.byteOffset + index_accesor.byteOffset); 169 | switch (index_accesor.componentType) { 170 | case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT: { 171 | const uint32_t *p = (uint32_t*) base; 172 | for (size_t i = 0; i < index_accesor.count; ++i) { 173 | func(model, p[i]); 174 | } 175 | }; break; 176 | case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: { 177 | const uint16_t *p = (uint16_t*) base; 178 | for (size_t i = 0; i < index_accesor.count; ++i) { 179 | func(model, p[i]); 180 | } 181 | }; break; 182 | case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: { 183 | const uint8_t *p = (uint8_t*) base; 184 | for (size_t i = 0; i < index_accesor.count; ++i) { 185 | func(model, p[i]); 186 | } 187 | }; break; 188 | } 189 | } 190 | 191 | static void ExtractVertexData(uint32_t v_pos, const uint8_t *base, int accesor_componentType, int accesor_type, bool accesor_normalized, uint32_t byteStride, float *output, uint8_t max_num_comp) { 192 | float v[4] = {0.0f, 0.0f, 0.0f, 0.0f}; 193 | uint32_t ncomp = 1; 194 | switch (accesor_type) { 195 | case TINYGLTF_TYPE_SCALAR: ncomp = 1; break; 196 | case TINYGLTF_TYPE_VEC2: ncomp = 2; break; 197 | case TINYGLTF_TYPE_VEC3: ncomp = 3; break; 198 | case TINYGLTF_TYPE_VEC4: ncomp = 4; break; 199 | default: 200 | assert(!"invalid type"); 201 | } 202 | switch (accesor_componentType) { 203 | case TINYGLTF_COMPONENT_TYPE_FLOAT: { 204 | const float *data = (float*)(base+byteStride*v_pos); 205 | for (uint32_t i = 0; (i < ncomp); ++i) { 206 | v[i] = data[i]; 207 | } 208 | } 209 | // TODO SUPPORT OTHER FORMATS 210 | break; 211 | default: 212 | assert(!"Conversion Type from float to -> ??? not implemented yet"); 213 | break; 214 | } 215 | for (uint32_t i = 0; i < max_num_comp; ++i) { 216 | output[i] = v[i]; 217 | } 218 | } 219 | 220 | struct MaterialCache { 221 | // matp between GLTF materials, and px_render materials 222 | std::map index; 223 | std::vector textures; 224 | std::vector materials; 225 | std::map, uint32_t> texture_index; 226 | px_render::DisplayList texture_dl; 227 | 228 | auto find(const tinygltf::Model &model, const tinygltf::Material &mat, const char *name, bool *found) -> decltype(mat.values.find(name)) { 229 | *found = true; 230 | auto const &i = mat.values.find(name); 231 | if (i != mat.values.end()) return i; 232 | auto const &ai = mat.additionalValues.find(name); 233 | if (ai != mat.additionalValues.end()) return ai; 234 | *found = false; 235 | return mat.values.end(); 236 | } 237 | 238 | static SamplerFiltering::Enum TranslateFiltering(int i) { 239 | switch (i) { 240 | case TINYGLTF_TEXTURE_FILTER_LINEAR: 241 | return SamplerFiltering::Linear; 242 | case TINYGLTF_TEXTURE_FILTER_NEAREST: 243 | return SamplerFiltering::Nearest; 244 | case TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR: 245 | return SamplerFiltering::LinearMipmapLinear; 246 | case TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_NEAREST: 247 | return SamplerFiltering::LinearMipmapNearest; 248 | case TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR: 249 | return SamplerFiltering::NearestMipmapLinear; 250 | case TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_NEAREST: 251 | return SamplerFiltering::NearestMipmapNearest; 252 | } 253 | return SamplerFiltering::Linear; // (default?) 254 | 255 | } 256 | 257 | static SamplerWrapping::Enum TranslateWrapping(int i) { 258 | switch (i) { 259 | case TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE: 260 | return SamplerWrapping::Clamp; 261 | case TINYGLTF_TEXTURE_WRAP_MIRRORED_REPEAT: 262 | return SamplerWrapping::MirroredRepeat; 263 | case TINYGLTF_TEXTURE_WRAP_REPEAT: 264 | return SamplerWrapping::Repeat; 265 | } 266 | return SamplerWrapping::Clamp; // (default?) 267 | } 268 | 269 | int32_t texture(px_render::RenderContext *ctx, const tinygltf::Model &model, const tinygltf::Material &mat, const char *name) { 270 | bool found; 271 | auto i = find(model, mat, name, &found); 272 | if (found) { 273 | const auto &j = i->second.json_double_value.find("index"); 274 | if (j != i->second.json_double_value.end()) { 275 | int gltf_texture_pos = (int) j->second; 276 | assert(gltf_texture_pos >= 0 && "Invalid texture index"); 277 | // extract texture data from model, first, then search for 278 | // existing texture in array 279 | const tinygltf::Texture &tex = model.textures[gltf_texture_pos]; 280 | int sampler_idx = tex.sampler; 281 | int source_idx = tex.source; 282 | auto found = texture_index.find(std::make_pair(source_idx, sampler_idx)); 283 | if (found != texture_index.end()) { 284 | return found->second; 285 | } 286 | // new one 287 | GLTF::Texture new_texture; 288 | const tinygltf::Image &image = model.images[source_idx]; 289 | new_texture.info.width = image.width; 290 | new_texture.info.height = image.height; 291 | new_texture.info.depth = 1; 292 | new_texture.info.type = TextureType::T2D; 293 | new_texture.info.usage = Usage::Static; 294 | 295 | switch(image.component) { 296 | case 1: new_texture.info.format = px_render::TexelsFormat::R_U8; break; 297 | case 2: new_texture.info.format = px_render::TexelsFormat::RG_U8; break; 298 | case 3: new_texture.info.format = px_render::TexelsFormat::RGB_U8; break; 299 | case 4: new_texture.info.format = px_render::TexelsFormat::RGBA_U8; break; 300 | default: 301 | assert(!"Invalid format..."); 302 | } 303 | 304 | if (model.samplers.size() > 0) 305 | { 306 | const tinygltf::Sampler &sampler = model.samplers[sampler_idx]; 307 | new_texture.info.magnification_filter = TranslateFiltering(sampler.magFilter); 308 | new_texture.info.minification_filter = TranslateFiltering(sampler.minFilter); 309 | new_texture.info.wrapping[0] = TranslateWrapping(sampler.wrapR); 310 | new_texture.info.wrapping[1] = TranslateWrapping(sampler.wrapS); 311 | new_texture.info.wrapping[2] = TranslateWrapping(sampler.wrapT); 312 | } 313 | 314 | new_texture.tex = ctx->createTexture(new_texture.info); 315 | texture_dl.fillTextureCommand().set_texture(new_texture.tex).set_data(&image.image[0]).set_build_mipmap(true); 316 | texture_dl.commitLastCommand(); 317 | 318 | // store 319 | int32_t new_texture_index = textures.size(); 320 | textures.push_back(std::move(new_texture)); 321 | texture_index[std::make_pair(source_idx, sampler_idx)] = new_texture_index; 322 | return new_texture_index; 323 | } 324 | } 325 | return -1; 326 | } 327 | 328 | float texture_scale(const tinygltf::Model &model, const tinygltf::Material &mat, const char *name) { 329 | bool found; 330 | auto i = find(model, mat, name, &found); 331 | if (found) { 332 | const auto &j = i->second.json_double_value.find("scale"); 333 | if (j != i->second.json_double_value.end()) { 334 | return j->second; 335 | } 336 | } 337 | return 1.0f; 338 | } 339 | Vec4 factor4(const tinygltf::Model &model, const tinygltf::Material &mat, const char *name, float df) { 340 | bool found; 341 | auto i = find(model, mat, name, &found); 342 | if (found) { 343 | auto cf = i->second.ColorFactor(); 344 | return Vec4 { 345 | (float)cf[0], (float)cf[1], (float)cf[2], (float)cf[3], 346 | }; 347 | } 348 | return Vec4{df,df,df,df}; 349 | } 350 | Vec3 factor3(const tinygltf::Model &model, const tinygltf::Material &mat, const char *name, float df) { 351 | bool found; 352 | auto i = find(model, mat, name, &found); 353 | if (found) { 354 | auto cf = i->second.ColorFactor(); 355 | return Vec3 { 356 | (float)cf[0], (float)cf[1], (float)cf[2] 357 | }; 358 | } 359 | return Vec3{df,df,df}; 360 | } 361 | float factor1(const tinygltf::Model &model, const tinygltf::Material &mat, const char *name, float df) { 362 | bool found; 363 | auto i = find(model, mat, name, &found); 364 | if (found) { 365 | return i->second.Factor(); 366 | } 367 | return df; 368 | } 369 | 370 | 371 | void load(px_render::RenderContext*ctx, const tinygltf::Model &model, int material_index) { 372 | if (index.find(material_index) != index.end()) return; 373 | const tinygltf::Material &mat = model.materials[material_index]; 374 | GLTF::Material material; 375 | material.name = mat.name; 376 | material.base_color.texture = texture(ctx, model, mat, "baseColorTexture"); 377 | material.base_color.factor = factor4(model, mat, "baseColorFactor", 1.0f); 378 | material.emmisive.texture = texture(ctx, model, mat, "emissiveTexture"); 379 | material.emmisive.factor = factor3(model, mat, "emissiveFactor",0.0f); 380 | material.metallic_roughness.texture = texture(ctx, model, mat, "metallicRoughnessTexture"); 381 | material.metallic_roughness.metallic_factor = factor1(model, mat, "metallicFactor", 0.5f); 382 | material.metallic_roughness.roughness_factor = factor1(model, mat, "roughnessFactor", 0.5f); 383 | material.normal.texture = texture(ctx, model,mat, "normalTexture"); 384 | material.normal.factor = texture_scale(model, mat, "normalTexture"); 385 | 386 | index[material_index] = materials.size(); 387 | materials.push_back(std::move(material)); 388 | } 389 | }; // Material Cache 390 | } // GLTF_Imp 391 | 392 | void GLTF::init(RenderContext *_ctx, const tinygltf::Model &model, uint32_t flags) { 393 | freeResources(); 394 | ctx = _ctx; 395 | // nodes 1st pass, count number of nodes+primitives 396 | uint32_t total_nodes = 1; // always add one artificial root node 397 | uint32_t total_primitives = 0; 398 | uint32_t total_num_vertices = 0; 399 | uint32_t total_num_indices = 0; 400 | GLTF_Imp::MaterialCache material_cache; 401 | 402 | uint32_t const vertex_size = 0 403 | + (flags&Flags::Geometry_Position? sizeof(float)*3: 0) 404 | + (flags&Flags::Geometry_Normal? sizeof(float)*3: 0) 405 | + (flags&Flags::Geometry_TexCoord0? sizeof(float)*2: 0) 406 | + (flags&Flags::Geometry_Tangent? sizeof(float)*4: 0) 407 | ; 408 | 409 | GLTF_Imp::NodeTraverse(model, 410 | [&total_nodes, &total_primitives, &total_num_vertices, &total_num_indices, flags, _ctx, &material_cache] 411 | (const tinygltf::Model model, uint32_t n_pos, uint32_t p_pos) { 412 | const tinygltf::Node &n = model.nodes[n_pos]; 413 | total_nodes++; 414 | if (n.mesh >= 0) { 415 | const tinygltf::Mesh &mesh = model.meshes[n.mesh]; 416 | for(size_t i = 0; i < mesh.primitives.size(); ++i) { 417 | const tinygltf::Primitive &primitive = mesh.primitives[i]; 418 | if (primitive.indices >= 0) { 419 | uint32_t min_vertex_index = (uint32_t)-1; 420 | uint32_t max_vertex_index = 0; 421 | // TODO: It would be nice to have a cache of index accessors (key=pirmitive.indices) 422 | // that way if two or more geometries are identical (because they use the same index buffer) 423 | // then the can reuse the same vertex data. Currently, vertex data is extracted for every 424 | // primitive.... 425 | GLTF_Imp::IndexTraverse(model, model.accessors[primitive.indices], 426 | [&min_vertex_index, &max_vertex_index, &total_num_vertices, &total_num_indices] 427 | (const tinygltf::Model&, uint32_t index) { 428 | min_vertex_index = std::min(min_vertex_index, index); 429 | max_vertex_index = std::max(max_vertex_index, index); 430 | total_num_indices++; 431 | }); 432 | total_num_vertices += (max_vertex_index - min_vertex_index +1); 433 | 434 | if (flags & Flags::Material) { 435 | if (primitive.material >= 0) { 436 | material_cache.load(_ctx, model, primitive.material); 437 | } 438 | } 439 | 440 | total_primitives++; 441 | } 442 | } 443 | } 444 | }); 445 | 446 | nodes = std::unique_ptr(new Node[total_nodes]); 447 | primitives = std::unique_ptr(new Primitive[total_primitives]); 448 | std::unique_ptr vertex_data {new float[total_num_vertices*vertex_size/sizeof(float)]}; 449 | std::unique_ptr index_data {new uint32_t[total_num_indices]}; 450 | 451 | // fill with 0s vertex data 452 | memset(vertex_data.get(), 0, sizeof(float)*total_num_vertices); 453 | 454 | // nodes 2nd pass, gather info 455 | nodes[0].model = nodes[0].transform = Mat4::Identity(); 456 | nodes[0].parent = 0; 457 | auto node_map = std::unique_ptr(new uint32_t[model.nodes.size()]); 458 | uint32_t current_node = 1; 459 | uint32_t current_primitive = 0; 460 | uint32_t current_mesh = 0; 461 | uint32_t current_vertex = 0; 462 | uint32_t current_index = 0; 463 | GLTF_Imp::NodeTraverse(model, 464 | [¤t_node, ¤t_primitive, &node_map, ¤t_mesh, 465 | ¤t_vertex, ¤t_index, &index_data, &vertex_data, 466 | &material_cache, 467 | total_nodes, total_primitives, total_num_vertices, vertex_size, flags, this] 468 | (const tinygltf::Model &model, uint32_t n_pos, uint32_t p_pos) mutable { 469 | const tinygltf::Node &gltf_n = model.nodes[n_pos]; 470 | Node &node = nodes[current_node]; 471 | // gather node transform or compute it 472 | if (gltf_n.matrix.size() == 16) { 473 | for(size_t i = 0; i < 16; ++i) node.transform.f[i] = (float) gltf_n.matrix[i]; 474 | } else { 475 | Vec3 s = {1.0f, 1.0f, 1.0f}; 476 | Vec4 r = {0.0f, 1.0f, 0.0f, 0.0f}; 477 | Vec3 t = {0.0f, 0.0f, 0.0f}; 478 | if (gltf_n.scale.size() == 3) { 479 | s = Vec3{ 480 | (float) gltf_n.scale[0], 481 | (float) gltf_n.scale[1], 482 | (float) gltf_n.scale[2]}; 483 | } 484 | if (gltf_n.rotation.size() == 4) { 485 | r = Vec4{ 486 | (float) gltf_n.rotation[1], // axis-x 487 | (float) gltf_n.rotation[2], // axis-y 488 | (float) gltf_n.rotation[3], // axis-z 489 | (float) gltf_n.rotation[0]}; // angle 490 | } 491 | if (gltf_n.translation.size() == 3) { 492 | t = Vec3{ 493 | (float) gltf_n.translation[0], 494 | (float) gltf_n.translation[1], 495 | (float) gltf_n.translation[2]}; 496 | } 497 | node.transform = Mat4::SRT(s,r,t); 498 | } 499 | // compute node-parent relationship 500 | node_map[n_pos] = current_node; 501 | if (p_pos != GLTF_Imp::InvalidNode) { 502 | node.parent = node_map[p_pos]; 503 | node.model = Mat4::Mult(nodes[node.parent].model, node.transform); 504 | } else { 505 | node.parent = 0; 506 | node.model = node.transform; 507 | } 508 | // gather primitive info 509 | if (gltf_n.mesh >= 0) { 510 | const tinygltf::Mesh &mesh = model.meshes[gltf_n.mesh]; 511 | for(size_t i = 0; i < mesh.primitives.size(); ++i) { 512 | const tinygltf::Primitive &gltf_p = mesh.primitives[i]; 513 | if (gltf_p.indices >= 0) { 514 | uint32_t min_vertex_index = (uint32_t)-1; 515 | uint32_t max_vertex_index = 0; 516 | uint32_t index_count = 0; 517 | GLTF_Imp::IndexTraverse(model, model.accessors[gltf_p.indices], 518 | [&min_vertex_index, &max_vertex_index, ¤t_index, &index_count, &index_data, ¤t_vertex] 519 | (const tinygltf::Model&, uint32_t index) { 520 | min_vertex_index = std::min(min_vertex_index, index); 521 | max_vertex_index = std::max(max_vertex_index, index); 522 | index_data[current_index+index_count] = index; 523 | index_count++; 524 | }); 525 | 526 | Primitive &primitive = primitives[current_primitive]; 527 | primitive.node = current_node; 528 | primitive.mesh = current_mesh; 529 | primitive.index_count = index_count; 530 | primitive.index_offset = current_index; 531 | current_index += index_count; 532 | 533 | if (flags & Flags::Material) { 534 | if (gltf_p.material >= 0) { 535 | primitive.material = material_cache.index[gltf_p.material]; 536 | } 537 | } 538 | 539 | using AttribWritter = std::function ; 540 | 541 | AttribWritter w_position = [](float *w, uint32_t p) {}; 542 | AttribWritter w_normal = [](float *w, uint32_t p) {}; 543 | AttribWritter w_texcoord0 = [](float *w, uint32_t p) {}; 544 | AttribWritter w_tangent = [](float *w, uint32_t p) {}; 545 | 546 | uint32_t vertex_stride_float = vertex_size/sizeof(float); 547 | for (const auto &attrib : gltf_p.attributes) { 548 | AttribWritter *writter = nullptr; 549 | unsigned int max_components = 0; 550 | if ((flags & Flags::Geometry_Position) && attrib.first.compare("POSITION") == 0) { 551 | writter = &w_position; 552 | max_components = 3; 553 | } else if ((flags & Flags::Geometry_Normal) && attrib.first.compare("NORMAL") == 0) { 554 | writter = &w_normal; 555 | max_components = 3; 556 | } else if ((flags & Flags::Geometry_TexCoord0) && attrib.first.compare("TEXCOORD_0") == 0) { 557 | writter = &w_texcoord0; 558 | max_components = 2; 559 | } else if ((flags & Flags::Geometry_Tangent) && attrib.first.compare("TANGENT") == 0) { 560 | writter = &w_tangent; 561 | max_components = 4; 562 | } 563 | 564 | if (!writter) continue; 565 | 566 | const tinygltf::Accessor &accesor = model.accessors[attrib.second]; 567 | const tinygltf::BufferView &buffer_view = model.bufferViews[accesor.bufferView]; 568 | const tinygltf::Buffer &buffer = model.buffers[buffer_view.buffer]; 569 | const uint8_t* base = &buffer.data.at(buffer_view.byteOffset + accesor.byteOffset); 570 | int byteStride = accesor.ByteStride(buffer_view); 571 | const bool normalized = accesor.normalized; 572 | 573 | switch (accesor.type) { 574 | case TINYGLTF_TYPE_SCALAR: max_components = std::min(max_components, 1u); break; 575 | case TINYGLTF_TYPE_VEC2: max_components = std::min(max_components, 2u); break; 576 | case TINYGLTF_TYPE_VEC3: max_components = std::min(max_components, 3u); break; 577 | case TINYGLTF_TYPE_VEC4: max_components = std::min(max_components, 4u); break; 578 | } 579 | 580 | switch (accesor.componentType) { 581 | case TINYGLTF_COMPONENT_TYPE_FLOAT: *writter = 582 | [base, byteStride, max_components](float *w, uint32_t p) { 583 | const float *f = (const float *)(base + p*byteStride); 584 | for (unsigned int i = 0; i < max_components; ++i) { 585 | w[i] = f[i]; 586 | } 587 | }; break; 588 | case TINYGLTF_COMPONENT_TYPE_DOUBLE: *writter = 589 | [base, byteStride, max_components](float *w, uint32_t p) { 590 | const double*f = (const double*)(base + p*byteStride); 591 | for (unsigned int i = 0; i < max_components; ++i) { 592 | w[i] = (float)f[i]; 593 | } 594 | }; break; 595 | case TINYGLTF_COMPONENT_TYPE_BYTE: *writter = 596 | [base, byteStride, max_components,normalized](float *w, uint32_t p) { 597 | const int8_t *f = (const int8_t*)(base + p*byteStride); 598 | for (unsigned int i = 0; i < max_components; ++i) { 599 | w[i] = normalized?f[i]/(float)128:f[i]; 600 | } 601 | }; break; 602 | case TINYGLTF_COMPONENT_TYPE_SHORT: *writter = 603 | [base, byteStride, max_components,normalized](float *w, uint32_t p) { 604 | const int16_t *f = (const int16_t*)(base + p*byteStride); 605 | for (unsigned int i = 0; i < max_components; ++i) { 606 | w[i] = normalized?f[i]/(float)32768:f[i]; 607 | } 608 | }; break; 609 | case TINYGLTF_COMPONENT_TYPE_INT: *writter = 610 | [base, byteStride, max_components,normalized](float *w, uint32_t p) { 611 | const int32_t *f = (const int32_t*)(base + p*byteStride); 612 | for (unsigned int i = 0; i < max_components; ++i) { 613 | w[i] = normalized?f[i]/(float)2147483648:f[i]; 614 | } 615 | }; break; 616 | case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: *writter = 617 | [base, byteStride, max_components,normalized](float *w, uint32_t p) { 618 | const uint8_t *f = (const uint8_t*)(base + p*byteStride); 619 | for (unsigned int i = 0; i < max_components; ++i) { 620 | w[i] = normalized?f[i]/(float)255:f[i]; 621 | } 622 | }; break; 623 | case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: *writter = 624 | [base, byteStride, max_components,normalized](float *w, uint32_t p) { 625 | const uint16_t *f = (const uint16_t*)(base + p*byteStride); 626 | for (unsigned int i = 0; i < max_components; ++i) { 627 | w[i] = normalized?f[i]/(float)65535:f[i]; 628 | } 629 | }; break; 630 | case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT: *writter = 631 | [base, byteStride, max_components,normalized](float *w, uint32_t p) { 632 | const uint32_t *f = (const uint32_t*)(base + p*byteStride); 633 | for (unsigned int i = 0; i < max_components; ++i) { 634 | w[i] = normalized?f[i]/(float)4294967295:f[i]; 635 | } 636 | }; break; 637 | default: 638 | assert(!"Not supported component type (yet)"); 639 | } 640 | } 641 | 642 | uint32_t vertex_offset = 0; 643 | 644 | if (flags & Flags::Geometry_Position) { 645 | for (uint32_t i = 0 ; i <= max_vertex_index-min_vertex_index; ++i) { 646 | w_position(&vertex_data[vertex_offset+(current_vertex+i)*vertex_stride_float], i+min_vertex_index); 647 | } 648 | if (flags & Flags::ComputeBounds) { 649 | bounds_min = { FLT_MAX, FLT_MAX, FLT_MAX }; 650 | bounds_max = { FLT_MIN, FLT_MIN, FLT_MIN }; 651 | for (uint32_t i = 0; i <= max_vertex_index - min_vertex_index; ++i) { 652 | Vec3 v = *(const Vec3*)&vertex_data[vertex_offset + (current_vertex + i)*vertex_stride_float]; 653 | Vec4 vt = Mat4::Mult(node.model, Vec4{ v.v.x, v.v.y, v.v.z, 1.0f }); 654 | vt.v.x /= vt.v.w; 655 | vt.v.y /= vt.v.w; 656 | vt.v.z /= vt.v.w; 657 | primitive.bounds_min.v.x = std::min(primitive.bounds_min.v.x, vt.v.x); 658 | primitive.bounds_min.v.y = std::min(primitive.bounds_min.v.y, vt.v.y); 659 | primitive.bounds_min.v.z = std::min(primitive.bounds_min.v.z, vt.v.z); 660 | primitive.bounds_max.v.x = std::max(primitive.bounds_max.v.x, vt.v.x); 661 | primitive.bounds_max.v.y = std::max(primitive.bounds_max.v.y, vt.v.y); 662 | primitive.bounds_max.v.z = std::max(primitive.bounds_max.v.z, vt.v.z); 663 | } 664 | } 665 | vertex_offset += 3; 666 | } 667 | if (flags & Flags::Geometry_Normal) { 668 | for (uint32_t i = 0; i <= max_vertex_index-min_vertex_index; ++i) { 669 | w_normal(&vertex_data[vertex_offset+(current_vertex+i)*vertex_stride_float], i+min_vertex_index); 670 | } 671 | vertex_offset += 3; 672 | } 673 | if (flags & Flags::Geometry_TexCoord0) { 674 | for (uint32_t i = 0; i <= max_vertex_index-min_vertex_index; ++i) { 675 | w_texcoord0(&vertex_data[vertex_offset+(current_vertex+i)*vertex_stride_float], i+min_vertex_index); 676 | } 677 | vertex_offset += 2; 678 | } 679 | if (flags & Flags::Geometry_Tangent) { 680 | for (uint32_t i = 0; i <= max_vertex_index - min_vertex_index; ++i) { 681 | w_texcoord0(&vertex_data[vertex_offset + (current_vertex + i)*vertex_stride_float], i + min_vertex_index); 682 | } 683 | vertex_offset += 4; 684 | } 685 | 686 | for (uint32_t i = 0; i < primitive.index_count; ++i) { 687 | index_data[primitive.index_offset+i] += current_vertex; 688 | index_data[primitive.index_offset+i] -= min_vertex_index; 689 | } 690 | 691 | current_vertex += max_vertex_index - min_vertex_index+1; 692 | current_primitive++; 693 | } 694 | } 695 | current_mesh++; 696 | } 697 | current_node++; 698 | }); 699 | 700 | num_textures = material_cache.textures.size(); 701 | num_materials = material_cache.materials.size(); 702 | textures = std::unique_ptr(new Texture[num_textures]); 703 | materials = std::unique_ptr(new Material[num_materials]); 704 | for(uint32_t i = 0; i < num_textures; ++i) { textures[i] = material_cache.textures[i]; } 705 | for(uint32_t i = 0; i < num_materials; ++i) { materials[i] = material_cache.materials[i]; } 706 | 707 | ctx->submitDisplayList(std::move(material_cache.texture_dl)); 708 | DisplayList dl; 709 | vertex_buffer = ctx->createBuffer({BufferType::Vertex, vertex_size*total_num_vertices, Usage::Static}); 710 | index_buffer = ctx->createBuffer({BufferType::Index, sizeof(uint32_t)*total_num_indices, Usage::Static}); 711 | dl.fillBufferCommand() 712 | .set_buffer(vertex_buffer) 713 | .set_data(vertex_data.get()) 714 | .set_size(vertex_size*total_num_vertices); 715 | dl.fillBufferCommand() 716 | .set_buffer(index_buffer) 717 | .set_data(index_data.get()) 718 | .set_size(sizeof(uint32_t)*total_num_indices); 719 | ctx->submitDisplayList(std::move(dl)); 720 | 721 | num_nodes = total_nodes; 722 | num_primitives = total_primitives; 723 | 724 | if (flags & Flags::ComputeBounds) { 725 | bounds_min = {FLT_MAX, FLT_MAX, FLT_MAX}; 726 | bounds_max = {FLT_MIN, FLT_MIN, FLT_MIN}; 727 | for (uint32_t i = 0; i < total_primitives; ++i) { 728 | Vec3 bmin = primitives[i].bounds_min; 729 | Vec3 bmax = primitives[i].bounds_max; 730 | bounds_min.v.x = std::min(bounds_min.v.x, bmin.v.x); 731 | bounds_min.v.y = std::min(bounds_min.v.y, bmin.v.y); 732 | bounds_min.v.z = std::min(bounds_min.v.z, bmin.v.z); 733 | bounds_max.v.x = std::max(bounds_max.v.x, bmax.v.x); 734 | bounds_max.v.y = std::max(bounds_max.v.y, bmax.v.y); 735 | bounds_max.v.z = std::max(bounds_max.v.z, bmax.v.z); 736 | } 737 | } 738 | } 739 | 740 | void GLTF::freeResources() { 741 | if (num_nodes) { 742 | num_nodes = 0; 743 | num_primitives = 0; 744 | nodes.reset(); 745 | primitives.reset(); 746 | DisplayList dl; 747 | dl.destroy(vertex_buffer); 748 | dl.destroy(index_buffer); 749 | ctx->submitDisplayList(std::move(dl)); 750 | } 751 | } 752 | } 753 | 754 | #endif // PX_RENDER_GLTF_IMPLEMENTATION -------------------------------------------------------------------------------- /px_sched.h: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright (c) 2017-2023 Jose L. Hidalgo (PpluX) 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | ----------------------------------------------------------------------------- */ 21 | 22 | // USAGE 23 | // Include this file in any file it needs to refer to it, but if you need 24 | // to change any define, or use a custom job description is preferable to 25 | // create a header that sets the defines, defines the job description and 26 | // finally includes this file. 27 | // 28 | // In *ONE* C++ file you need to declare 29 | // #define PX_SCHED_IMPLEMENTATION 1 30 | // before including the file that contains px_sched.h 31 | 32 | #ifndef PX_SCHED 33 | #define PX_SCHED 34 | 35 | // -- Job Object ---------------------------------------------------------- 36 | // if you want to use your own job objects, define the following 37 | // macro and provide a struct px_sched::Job object with an operator() method. 38 | // 39 | // Example, a C-like pointer to function: 40 | // 41 | // #define PX_SCHED_CUSTOM_JOB_DEFINITION 42 | // namespace px_sched { 43 | // struct Job { 44 | // void (*func)(void *arg); 45 | // void *arg; 46 | // void operator()() { func(arg); } 47 | // }; 48 | // } // px namespace 49 | // 50 | // By default Jobs are simply std::function 51 | // 52 | #ifndef PX_SCHED_CUSTOM_JOB_DEFINITION 53 | #include 54 | namespace px_sched { 55 | typedef std::function Job; 56 | } // px namespace 57 | #endif 58 | // ----------------------------------------------------------------------------- 59 | 60 | // -- Backend selection -------------------------------------------------------- 61 | // Right now there is only two backends(single-threaded, and regular threads), 62 | // in the future we will add windows-fibers and posix-ucontext. Meanwhile try 63 | // to avoid waitFor(...) and use more runAfter if possible. Try not to suspend 64 | // threads on external mutexes. 65 | #if !defined(PX_SCHED_CONFIG_SINGLE_THREAD) && \ 66 | !defined(PX_SCHED_CONFIG_REGULAR_THREADS) 67 | # define PX_SCHED_CONFIG_REGULAR_THREADS 1 68 | #endif 69 | 70 | #ifdef PX_SCHED_CONFIG_REGULAR_THREADS 71 | # define PX_SCHED_IMP_REGULAR_THREADS 1 72 | #else 73 | # define PX_SCHED_IMP_REGULAR_THREADS 0 74 | #endif 75 | 76 | #ifdef PX_SCHED_CONFIG_SINGLE_THREAD 77 | # define PX_SCHED_IMP_SINGLE_THREAD 1 78 | #else 79 | # define PX_SCHED_IMP_SINGLE_THREAD 0 80 | #endif 81 | 82 | #if ( PX_SCHED_IMP_SINGLE_THREAD \ 83 | + PX_SCHED_IMP_REGULAR_THREADS \ 84 | ) != 1 85 | #error "PX_SCHED: Only one backend must be enabled (and at least one)" 86 | #endif 87 | // ----------------------------------------------------------------------------- 88 | 89 | #ifndef PX_SCHED_CACHE_LINE_SIZE 90 | #define PX_SCHED_CACHE_LINE_SIZE 64 91 | #endif 92 | // ----------------------------------------------------------------------------- 93 | 94 | 95 | // some checks, can be omitted if you're confident there is no 96 | // misuse of the library. 97 | #ifndef PX_SCHED_DOES_CHECKS 98 | #define PX_SCHED_DOES_CHECKS 1 99 | #endif 100 | 101 | // PX_SCHED_CHECK_FN ----------------------------------------------------------- 102 | // used to check conditions, to test for erros 103 | // defined only if PX_SCHED_DOES_CHECKS is evaluated to true. By 104 | // default uses printf and abort, use your own implementation by defining 105 | // PX_SCHED_CHECK_FN before including this header. 106 | #ifndef PX_SCHED_CHECK_FN 107 | # if PX_SCHED_DOES_CHECKS 108 | # include 109 | # include 110 | # define PX_SCHED_CHECK_FN(cond, ...) \ 111 | if (!(cond)) { \ 112 | printf("-- PX_SCHED ERROR: -------------------\n"); \ 113 | printf("-- %s:%d\n", __FILE__, __LINE__); \ 114 | printf("--------------------------------------\n"); \ 115 | printf(__VA_ARGS__);\ 116 | printf("\n--------------------------------------\n"); \ 117 | abort(); \ 118 | } 119 | # else 120 | # define PX_SCHED_CHECK_FN(...) /*NO CHECK*/ 121 | # endif // PX_SCHED_DOES_CHECKS 122 | #endif // PX_SCHED_CHECK_FN 123 | 124 | // Function called at the begining of some functions to be able to 125 | // monitor/trace the scheduler. It will be called as PX_SCHED_TRACE_FN("Name") and 126 | // always in the scope to measure. 127 | #ifndef PX_SCHED_TRACE_FN 128 | #define PX_SCHED_TRACE_FN(...) /* NO TRACING */ 129 | #endif 130 | 131 | #include 132 | #include 133 | #include 134 | 135 | namespace px_sched { 136 | 137 | // Sync object 138 | class Sync { 139 | uint32_t hnd = 0; 140 | friend class Scheduler; 141 | }; 142 | 143 | 144 | struct MemCallbacks { 145 | void* (*alloc_fn)(size_t alignment, size_t amount) = [](size_t a, size_t s) { 146 | // The spec requires that aligned allogs alignments can't be smaller than 147 | // sizeof(void*) 148 | if (a < sizeof(void*)) a = sizeof(void*); 149 | void *ptr = aligned_alloc(a, s); 150 | PX_SCHED_CHECK_FN(ptr != nullptr, "Invalid mem alloc"); 151 | return ptr; 152 | }; 153 | void (*free_fn)(void *ptr) = ::free; 154 | }; 155 | 156 | struct SchedulerParams { 157 | uint16_t num_threads = 16; // num OS threads created 158 | uint16_t max_running_threads = 0; // 0 --> will be set to max hardware concurrency 159 | uint16_t max_number_tasks = 1024; // max number of simultaneous tasks 160 | uint16_t thread_num_tries_on_idle = 1; // number of tries before suspend the thread 161 | uint32_t thread_sleep_on_idle_in_microseconds = 1; // time spent waiting between tries 162 | MemCallbacks mem_callbacks; 163 | }; 164 | 165 | // -- Atomic ----------------------------------------------------------------- 166 | template 167 | struct Atomic { 168 | #if PX_SCHED_IMP_SINGLE_THREAD 169 | T data = {}; 170 | Atomic() = default; 171 | ~Atomic() = default; 172 | explicit Atomic(const T&d) : data(d) {} 173 | bool compare_exchange_strong(T ¤t, const T &next) { 174 | if (data == current) { 175 | data = next; 176 | return true; 177 | } 178 | current = data; 179 | return false; 180 | } 181 | bool compare_exchange_weak(T ¤t, const T &next) { 182 | return compare_exchange_strong(current,next); 183 | } 184 | const T load() const { return data; } 185 | void store(const T& v) { data = v; } 186 | T fetch_add(const T&v) { 187 | T fetch(data); 188 | data += v; 189 | return fetch; 190 | } 191 | T fetch_sub(const T&v) { 192 | T fetch(data); 193 | data -= v; 194 | return fetch; 195 | } 196 | T exchange(const T&v) { 197 | T old(data); 198 | data = v; 199 | return old; 200 | } 201 | bool operator==(const Atomic &other) const { return other.data == data; } 202 | bool operator==(const T &other) const { return other== data; } 203 | #else 204 | std::atomic data = {}; 205 | Atomic() = default; 206 | ~Atomic() = default; 207 | explicit Atomic(const T&d) : data(d) {} 208 | bool compare_exchange_strong(T ¤t, const T &next) { 209 | return data.compare_exchange_strong(current, next); 210 | } 211 | bool compare_exchange_weak(T ¤t, const T &next) { 212 | return data.compare_exchange_weak(current, next); 213 | } 214 | const T load() const { return data.load(); } 215 | void store(const T& v) { data = v; } 216 | T fetch_add(const T&v) { return data.fetch_add(v); } 217 | T fetch_sub(const T&v) { return data.fetch_sub(v); } 218 | T exchange(const T&v) { return data.exchange(v); } 219 | bool operator==(const Atomic &other) const { return other.data == data; } 220 | bool operator==(const T &other) const { return other== data; } 221 | #endif 222 | }; 223 | 224 | 225 | // -- ObjectPool ------------------------------------------------------------- 226 | // holds up to 2^20 objects with ref counting and versioning 227 | // used internally by the Scheduler for tasks and counters, but can also 228 | // be used as a thread-safe object pool 229 | 230 | template 231 | struct ObjectPool { 232 | const uint32_t kPosMask = 0x000FFFFF; // 20 bits 233 | const uint32_t kRefMask = kPosMask; // 20 bits 234 | const uint32_t kVerMask = 0xFFF00000; // 12 bits 235 | const uint32_t kVerDisp = 20; 236 | 237 | ~ObjectPool(); 238 | 239 | void init(uint32_t count, const MemCallbacks &mem = MemCallbacks()); 240 | void reset(); 241 | 242 | // only access objects you've previously referenced 243 | T& get(uint32_t hnd); 244 | 245 | // only access objects you've previously referenced 246 | const T& get(uint32_t hnd) const; 247 | 248 | // given a position inside the pool returns the handler and also current ref 249 | // count and current version number (only used for debugging) 250 | uint32_t info(uint32_t pos, uint32_t *count, uint32_t *ver) const; 251 | 252 | // max number of elements hold by the object pool 253 | uint32_t size() const { return count_; } 254 | 255 | // returns the handler of an object in the pool that can be used 256 | // it also increments in one the number of references (no need to call ref) 257 | uint32_t adquireAndRef(); 258 | 259 | void unref(uint32_t hnd) const; 260 | 261 | // decrements the counter, if the object is no longer valid (last ref) 262 | // the given function will be executed with the element 263 | template 264 | void unref(uint32_t hnd, F &&f) const; 265 | 266 | // returns true if the given position was a valid object 267 | bool ref(uint32_t hnd) const; 268 | 269 | uint32_t refCount(uint32_t hnd) const; 270 | 271 | uint32_t in_use() const { return in_use_.load();} 272 | 273 | private: 274 | void newElement(uint32_t pos) const; 275 | void deleteElement(uint32_t pos) const; 276 | 277 | struct alignas(PX_SCHED_CACHE_LINE_SIZE) D { 278 | mutable Atomic state; 279 | uint32_t version = 0; 280 | T element; 281 | }; // D struct 282 | 283 | mutable Atomic in_use_; 284 | Atomic next_; 285 | D *data_ = nullptr; 286 | uint32_t count_ = 0; 287 | MemCallbacks mem_; 288 | }; 289 | 290 | 291 | class Scheduler { 292 | public: 293 | Scheduler(); 294 | ~Scheduler(); 295 | 296 | void init(const SchedulerParams ¶ms = SchedulerParams()); 297 | void stop(); 298 | 299 | void run(Job &&job, Sync *out_sync_obj = nullptr); 300 | void runAfter(Sync sync,Job &&job, Sync *out_sync_obj = nullptr); 301 | void waitFor(Sync sync); //< suspend current thread 302 | 303 | // returns the number of tasks not yet finished associated to the sync object 304 | // thus 0 means all of them has finished (or the sync object was empty, or 305 | // unused) 306 | uint32_t numPendingTasks(Sync s); 307 | 308 | bool hasFinished(Sync s) { return numPendingTasks(s) == 0; } 309 | 310 | // Call this only to print the internal state of the scheduler, mainly if it 311 | // stops working and want to see who is waiting for what, and so on. 312 | void getDebugStatus(char *buffer, size_t buffer_size); 313 | 314 | // manually increment the value of a Sync object. Sync objects triggers 315 | // when they reach 0. 316 | // *WARNING*: calling increment without a later decrement might leave 317 | // tasks waiting forever, and will leak resources. 318 | void incrementSync(Sync *s); 319 | 320 | // manually decrement the value of a Sync object. 321 | // *WARNING*: never call decrement on a sync object wihtout calling 322 | // @incrementSync first. 323 | void decrementSync(Sync *s); 324 | 325 | // By default workers will be named as Worker-id 326 | // The pointer passed here must be valid until set_current_thread is called 327 | // again... 328 | static void set_current_thread_name(const char *name); 329 | static const char *current_thread_name(); 330 | 331 | const SchedulerParams& params() const { return params_; } 332 | 333 | // Number of active threads (executing tasks) 334 | uint32_t active_threads() const { return active_threads_.load(); } 335 | 336 | uint32_t num_tasks() const { return tasks_.in_use(); } 337 | uint32_t num_counters() const { return counters_.in_use(); } 338 | 339 | #if PX_SCHED_IMP_REGULAR_THREADS 340 | uint32_t num_tasks_ready() { return ready_tasks_.in_use(); } 341 | #endif 342 | 343 | #if PX_SCHED_IMP_SINGLE_THREAD 344 | uint32_t num_tasks_ready() { return 0; } 345 | #endif 346 | 347 | private: 348 | struct TLS; 349 | static TLS* tls(); 350 | void wakeUpOneThread(); 351 | SchedulerParams params_; 352 | Atomic active_threads_; 353 | Atomic running_; 354 | 355 | struct WaitFor; 356 | 357 | struct Task { 358 | Job job; 359 | uint32_t counter_id = 0; 360 | Atomic next_sibling_task; 361 | }; 362 | 363 | struct Counter { 364 | Atomic task_id; 365 | Atomic user_count; 366 | WaitFor *wait_ptr = nullptr; 367 | }; 368 | 369 | ObjectPool tasks_; 370 | ObjectPool counters_; 371 | uint32_t createTask(Job &&job, Sync *out_sync_obj); 372 | uint32_t createCounter(); 373 | void unrefCounter(uint32_t counter_hnd); 374 | 375 | #if PX_SCHED_IMP_REGULAR_THREADS 376 | struct IndexQueue { 377 | ~IndexQueue() { 378 | PX_SCHED_CHECK_FN(list_ == nullptr, "IndexQueue Resources leaked..."); 379 | } 380 | void reset() { 381 | if (list_) { 382 | mem_.free_fn(list_); 383 | list_ = nullptr; 384 | } 385 | size_ = 0; 386 | in_use_ = 0; 387 | } 388 | void init(uint16_t max, const MemCallbacks &mem_cb = MemCallbacks()) { 389 | _lock(); 390 | reset(); 391 | mem_ = mem_cb; 392 | size_ = max; 393 | in_use_ = 0; 394 | list_ = static_cast(mem_.alloc_fn(alignof(uint32_t), sizeof(uint32_t)*size_)); 395 | _unlock(); 396 | } 397 | void push(uint32_t p) { 398 | _lock(); 399 | PX_SCHED_CHECK_FN(in_use_ < size_, "IndexQueue Overflow total in use %hu (max %hu)", in_use_, size_); 400 | uint16_t pos = (current_ + in_use_)%size_; 401 | list_[pos] = p; 402 | in_use_++; 403 | _unlock(); 404 | } 405 | uint16_t in_use() { 406 | _lock(); 407 | uint16_t result = in_use_; 408 | _unlock(); 409 | return result; 410 | } 411 | bool pop(uint32_t *res) { 412 | _lock(); 413 | bool result = false; 414 | if (in_use_) { 415 | if (res) *res = list_[current_]; 416 | current_ = (current_+1)%size_; 417 | in_use_--; 418 | result = true; 419 | } 420 | _unlock(); 421 | return result; 422 | } 423 | void _unlock() { lock_.clear(std::memory_order_release); } 424 | void _lock() { 425 | while(lock_.test_and_set(std::memory_order_acquire)) { 426 | std::this_thread::yield(); 427 | } 428 | } 429 | uint32_t *list_ = nullptr; 430 | std::atomic_flag lock_ = ATOMIC_FLAG_INIT; 431 | MemCallbacks mem_; 432 | volatile uint16_t size_ = 0; 433 | volatile uint16_t in_use_ = 0; 434 | volatile uint16_t current_ = 0; 435 | }; 436 | 437 | struct WaitFor { 438 | explicit WaitFor() 439 | : owner(std::this_thread::get_id()) 440 | , ready(false) {} 441 | void wait() { 442 | PX_SCHED_TRACE_FN("WaitFor"); 443 | PX_SCHED_CHECK_FN(std::this_thread::get_id() == owner, 444 | "WaitFor::wait can only be invoked from the thread " 445 | "that created the object"); 446 | // TODO: instead of waiting, look for tasks that could be 447 | // done from this thread. 448 | std::unique_lock lk(mutex); 449 | if(!ready) { 450 | condition_variable.wait(lk); 451 | } 452 | } 453 | void signal() { 454 | if (owner != std::this_thread::get_id()) { 455 | std::lock_guard lk(mutex); 456 | ready = true; 457 | condition_variable.notify_all(); 458 | } else { 459 | ready = true; 460 | } 461 | } 462 | private: 463 | std::thread::id const owner; 464 | std::mutex mutex; 465 | std::condition_variable condition_variable; 466 | bool ready; 467 | }; 468 | 469 | struct Worker { 470 | std::thread thread; 471 | // set by the thread when is sleep 472 | Atomic wake_up; 473 | TLS *thread_tls = nullptr; 474 | uint16_t thread_index = 0xFFFF; 475 | }; 476 | 477 | uint16_t wakeUpThreads(uint16_t max_num_threads); 478 | 479 | Worker *workers_ = nullptr; 480 | IndexQueue ready_tasks_; 481 | 482 | static void WorkerThreadMain(Scheduler *schd, Worker *); 483 | #endif 484 | 485 | 486 | }; 487 | 488 | //-- Optional: Spinlock ------------------------------------------------------ 489 | class Spinlock { 490 | public: 491 | Spinlock() : owner_(std::thread::id()), count_(0) {} 492 | 493 | ~Spinlock() { 494 | lock(); 495 | } 496 | 497 | void lock() { 498 | while(!try_lock()) { std::this_thread::yield(); } 499 | } 500 | 501 | void unlock() { 502 | std::thread::id tid = std::this_thread::get_id(); 503 | PX_SCHED_CHECK_FN(owner_ == tid, "Invalid Spinlock::unlock owner mistmatch"); 504 | count_--; 505 | if (count_ == 0) { 506 | owner_.store(std::thread::id()); 507 | } 508 | } 509 | 510 | bool try_lock() { 511 | std::thread::id tid = std::this_thread::get_id(); 512 | if (owner_ == tid) { 513 | count_++; 514 | return true; 515 | } 516 | std::thread::id expected; 517 | if (owner_.compare_exchange_weak(expected, tid)) { 518 | count_ = 1; 519 | return true; 520 | } 521 | return false; 522 | } 523 | private: 524 | Atomic owner_ ; 525 | uint32_t count_; 526 | }; 527 | 528 | //-- Object pool implementation ---------------------------------------------- 529 | template 530 | inline ObjectPool::~ObjectPool() { 531 | reset(); 532 | } 533 | 534 | template 535 | void ObjectPool::newElement(uint32_t pos) const { 536 | new (&data_[pos].element) T; 537 | uint32_t i = 1; 538 | in_use_.fetch_add(i); 539 | } 540 | 541 | template 542 | void ObjectPool::deleteElement(uint32_t pos) const { 543 | data_[pos].element.~T(); 544 | uint32_t i = 1; 545 | in_use_.fetch_sub(i); 546 | } 547 | 548 | template 549 | inline void ObjectPool::init(uint32_t count, const MemCallbacks &mem_cb) { 550 | reset(); 551 | mem_ = mem_cb; 552 | data_ = static_cast(mem_.alloc_fn(alignof(D),sizeof(D)*count)); 553 | for(uint32_t i = 0; i < count; ++i) { 554 | data_[i].state.store(0xFFFu<< kVerDisp); 555 | } 556 | count_ = count; 557 | next_.store(0); 558 | } 559 | 560 | template 561 | inline void ObjectPool::reset() { 562 | count_ = 0; 563 | next_.store(0); 564 | if (data_) { 565 | mem_.free_fn(data_); 566 | data_ = nullptr; 567 | } 568 | } 569 | 570 | // only access objects you've previously referenced 571 | template 572 | inline T& ObjectPool::get(uint32_t hnd) { 573 | uint32_t pos = hnd & kPosMask; 574 | PX_SCHED_CHECK_FN(pos < count_, "Invalid access to pos %u hnd:%u", pos, count_); 575 | return data_[pos].element; 576 | } 577 | 578 | // only access objects you've previously referenced 579 | template< class T> 580 | inline const T& ObjectPool::get(uint32_t hnd) const { 581 | uint32_t pos = hnd & kPosMask; 582 | PX_SCHED_CHECK_FN(pos < count_, "Invalid access to pos %u hnd:%u", pos, count_); 583 | return data_[pos].element; 584 | } 585 | 586 | template< class T> 587 | inline uint32_t ObjectPool::info(uint32_t pos, uint32_t *count, uint32_t *ver) const { 588 | PX_SCHED_CHECK_FN(pos < count_, "Invalid access to pos %u hnd:%u", pos, count_); 589 | uint32_t s = data_[pos].state.load(); 590 | if (count) *count = (s & kRefMask); 591 | if (ver) *ver = (s & kVerMask) >> kVerDisp; 592 | return (s&kVerMask) | pos; 593 | } 594 | 595 | template 596 | inline uint32_t ObjectPool::adquireAndRef() { 597 | PX_SCHED_TRACE_FN("ObjectPool::adquireAndRef"); 598 | uint32_t tries = 0; 599 | for(;;) { 600 | uint32_t pos = (next_.fetch_add(1)%count_); 601 | D& d = data_[pos]; 602 | uint32_t version = (d.state.load() & kVerMask) >> kVerDisp; 603 | // note: avoid 0 as version 604 | uint32_t newver = (version+1) & 0xFFF; 605 | if (newver == 0) newver = 1; 606 | // instead of using 1 as initial ref, we use 2, when we see 1 607 | // in the future we know the object must be freed, but it wont 608 | // be actually freed until it reaches 0 609 | uint32_t newvalue = (newver << kVerDisp) + 2; 610 | uint32_t expected = version << kVerDisp; 611 | if (d.state.compare_exchange_strong(expected, newvalue)) { 612 | newElement(pos); //< initialize 613 | return (newver << kVerDisp) | (pos & kPosMask); 614 | } 615 | tries++; 616 | // TODO... make this, optional... 617 | PX_SCHED_CHECK_FN(tries < count_*count_, "It was not possible to find a valid index after %u tries", tries); 618 | } 619 | } 620 | 621 | template< class T> 622 | inline void ObjectPool::unref(uint32_t hnd) const { 623 | uint32_t pos = hnd & kPosMask; 624 | uint32_t ver = (hnd & kVerMask); 625 | D& d = data_[pos]; 626 | for(;;) { 627 | uint32_t prev = d.state.load(); 628 | uint32_t next = prev - 1; 629 | PX_SCHED_CHECK_FN((prev & kVerMask) == ver, 630 | "Invalid unref HND = %u(%u), Versions: %u vs %u", 631 | pos, hnd, prev & kVerMask, ver); 632 | PX_SCHED_CHECK_FN((prev & kRefMask) > 1, 633 | "Invalid unref HND = %u(%u), invalid ref count", 634 | pos, hnd); 635 | if (d.state.compare_exchange_strong(prev, next)) { 636 | if ((next & kRefMask) == 1) { 637 | deleteElement(pos); 638 | d.state.store(0); 639 | } 640 | return; 641 | } 642 | } 643 | } 644 | 645 | // decrements the counter, if the object is no longer valid (last ref) 646 | // the given function will be executed with the element 647 | template 648 | template 649 | inline void ObjectPool::unref(uint32_t hnd, F &&f) const { 650 | uint32_t pos = hnd & kPosMask; 651 | uint32_t ver = (hnd & kVerMask); 652 | D& d = data_[pos]; 653 | for(;;) { 654 | uint32_t prev = d.state.load(); 655 | uint32_t next = prev - 1; 656 | PX_SCHED_CHECK_FN((prev & kVerMask) == ver, 657 | "Invalid unref HND = %u(%u), Versions: %u vs %u", 658 | pos, hnd, prev & kVerMask, ver); 659 | PX_SCHED_CHECK_FN((prev & kRefMask) > 1, 660 | "Invalid unref HND = %u(%u), invalid ref count", 661 | pos, hnd); 662 | if (d.state.compare_exchange_strong(prev, next)) { 663 | if ((next & kRefMask) == 1) { 664 | f(d.element); 665 | deleteElement(pos); 666 | d.state.store(0); 667 | } 668 | return; 669 | } 670 | } 671 | } 672 | 673 | template< class T> 674 | inline bool ObjectPool::ref(uint32_t hnd) const{ 675 | if (!hnd) return false; 676 | uint32_t pos = hnd & kPosMask; 677 | uint32_t ver = (hnd & kVerMask); 678 | D& d = data_[pos]; 679 | for (;;) { 680 | uint32_t prev = d.state.load(); 681 | uint32_t next_c =((prev & kRefMask) +1); 682 | if ((prev & kVerMask) != ver || next_c <= 2) return false; 683 | PX_SCHED_CHECK_FN(next_c == (next_c & kRefMask), "Too many references..."); 684 | uint32_t next = (prev & kVerMask) | next_c ; 685 | if (d.state.compare_exchange_strong(prev, next)) { 686 | return true; 687 | } 688 | } 689 | } 690 | 691 | template< class T> 692 | inline uint32_t ObjectPool::refCount(uint32_t hnd) const{ 693 | if (!hnd) return 0; 694 | uint32_t pos = hnd & kPosMask; 695 | uint32_t ver = (hnd & kVerMask); 696 | D& d = data_[pos]; 697 | uint32_t current = d.state.load(); 698 | if ((current & kVerMask) != ver ) return 0; 699 | return (current & kRefMask); 700 | } 701 | 702 | 703 | } // end of px namespace 704 | #endif // PX_SCHED 705 | 706 | //---------------------------------------------------------------------------- 707 | // -- IMPLEMENTATION --------------------------------------------------------- 708 | //---------------------------------------------------------------------------- 709 | 710 | #ifdef PX_SCHED_IMPLEMENTATION 711 | 712 | #ifndef PX_SCHED_IMPLEMENTATION_DONE 713 | #define PX_SCHED_IMPLEMENTATION_DONE 1 714 | 715 | #ifdef PX_SCHED_ATLERNATIVE_TLS 716 | // used to store custom TLS... some platform might have problems with 717 | // TLS (iOS used to be one) 718 | #include 719 | #endif 720 | 721 | namespace px_sched { 722 | 723 | struct Scheduler::TLS { 724 | const char *name = nullptr; 725 | Scheduler *scheduler = nullptr; 726 | }; 727 | 728 | Scheduler::TLS* Scheduler::tls() { 729 | #ifdef PX_SCHED_ATLERNATIVE_TLS 730 | static std::unordered_map data; 731 | static Atomic in_use(0); 732 | for(;;) { 733 | uint32_t expected = 0; 734 | if (in_use.compare_exchange_weak(expected, 1)) break; 735 | } 736 | auto result = &data[std::this_thread::get_id()]; 737 | in_use.store(0); 738 | return result; 739 | #else 740 | static thread_local TLS tls; 741 | return &tls; 742 | #endif 743 | } 744 | 745 | void Scheduler::set_current_thread_name(const char *name) { 746 | TLS *d = tls(); 747 | d->name = name; 748 | } 749 | 750 | const char *Scheduler::current_thread_name() { 751 | TLS *d = tls(); 752 | return d->name; 753 | } 754 | 755 | } 756 | 757 | // Common to all implementations of px_sched (Single Threaded and Multi Threaded) 758 | namespace px_sched { 759 | uint32_t Scheduler::createCounter() { 760 | PX_SCHED_TRACE_FN("CreateCounter"); 761 | uint32_t hnd = counters_.adquireAndRef(); 762 | Counter *c = &counters_.get(hnd); 763 | c->task_id.store(0); 764 | c->user_count.store(0); 765 | c->wait_ptr = nullptr; 766 | return hnd; 767 | } 768 | 769 | uint32_t Scheduler::createTask(Job &&job, Sync *sync_obj) { 770 | PX_SCHED_TRACE_FN("CreateTask"); 771 | uint32_t ref = tasks_.adquireAndRef(); 772 | Task *task = &tasks_.get(ref); 773 | task->job = std::move(job); 774 | task->counter_id = 0; 775 | task->next_sibling_task.store(0); 776 | if (sync_obj) { 777 | bool new_counter = !counters_.ref(sync_obj->hnd); 778 | if (new_counter) { 779 | sync_obj->hnd = createCounter(); 780 | } 781 | task->counter_id = sync_obj->hnd; 782 | } 783 | return ref; 784 | } 785 | 786 | void Scheduler::incrementSync(Sync *s) { 787 | PX_SCHED_TRACE_FN("IncrementSync"); 788 | if (!counters_.ref(s->hnd)) { 789 | s->hnd = createCounter(); 790 | } 791 | Counter &c = counters_.get(s->hnd); 792 | uint32_t prev = c.user_count.fetch_add(1); 793 | if (prev != 0) { 794 | unrefCounter(s->hnd); 795 | } 796 | } 797 | 798 | void Scheduler::decrementSync(Sync *s) { 799 | PX_SCHED_TRACE_FN("DecrementSync"); 800 | if (counters_.ref(s->hnd)) { 801 | Counter &c = counters_.get(s->hnd); 802 | uint32_t prev = c.user_count.fetch_sub(1); 803 | if (prev == 1) { 804 | // last one should unref twice 805 | unrefCounter(s->hnd); 806 | } 807 | unrefCounter(s->hnd); 808 | } 809 | } 810 | } 811 | 812 | #if PX_SCHED_IMP_SINGLE_THREAD 813 | 814 | namespace px_sched { 815 | Scheduler::Scheduler() {} 816 | Scheduler::~Scheduler() {} 817 | void Scheduler::init(const SchedulerParams ¶ms) { 818 | params_ = params; 819 | tasks_.init(params_.max_number_tasks, params_.mem_callbacks); 820 | counters_.init(params_.max_number_tasks, params_.mem_callbacks); 821 | } 822 | void Scheduler::stop() { 823 | tasks_.reset(); 824 | counters_.reset(); 825 | } 826 | void Scheduler::run(Job &&job, Sync *s) { 827 | job(); 828 | if (s) decrementSync(s); 829 | } 830 | 831 | void Scheduler::runAfter(Sync trigger, Job &&job, Sync *s) { 832 | if (counters_.ref(trigger.hnd)) { 833 | uint32_t t_ref = createTask(std::move(job), s); 834 | Counter *c = &counters_.get(trigger.hnd); 835 | for(;;) { 836 | uint32_t current = c->task_id.load(); 837 | if (c->task_id.compare_exchange_strong(current, t_ref)) { 838 | Task *task = &tasks_.get(t_ref); 839 | task->next_sibling_task.store(current); 840 | break; 841 | } 842 | } 843 | unrefCounter(trigger.hnd); 844 | } else { 845 | run(std::move(job), s); 846 | } 847 | } 848 | 849 | void Scheduler::waitFor(Sync s) { 850 | PX_SCHED_CHECK_FN(!(counters_.ref(s.hnd)), "Invalid, on SingleThreaded mode we can not wait for a sync object..."); 851 | } 852 | 853 | uint32_t Scheduler::numPendingTasks(Sync s){ 854 | return counters_.refCount(s.hnd); 855 | } 856 | 857 | void Scheduler::getDebugStatus(char *buffer, size_t buffer_size) { 858 | if (buffer_size) buffer[0] = 0; 859 | } 860 | 861 | void Scheduler::unrefCounter(uint32_t hnd) { 862 | if (counters_.ref(hnd)) { 863 | counters_.unref(hnd); 864 | Scheduler *schd = this; 865 | counters_.unref(hnd, [schd](Counter &c) { 866 | // wake up all tasks 867 | uint32_t tid = c.task_id.load(); 868 | while (schd->tasks_.ref(tid)) { 869 | Task &task = schd->tasks_.get(tid); 870 | uint32_t next_tid = task.next_sibling_task.load(); 871 | uint32_t counter_id = task.counter_id; 872 | task.next_sibling_task.store(0); 873 | task.job(); // execute the task 874 | schd->tasks_.unref(tid); 875 | schd->unrefCounter(counter_id); 876 | tid = next_tid; 877 | } 878 | }); 879 | } 880 | } 881 | 882 | void Scheduler::wakeUpOneThread() {} 883 | } // end of px namespace 884 | #endif // PX_SCHED_IMP_SINGLE_THREAD 885 | 886 | #if PX_SCHED_IMP_REGULAR_THREADS 887 | // Default implementation using threads 888 | #include 889 | namespace px_sched { 890 | Scheduler::Scheduler() { 891 | active_threads_.store(0); 892 | } 893 | 894 | Scheduler::~Scheduler() { stop(); } 895 | 896 | void Scheduler::init(const SchedulerParams &_params) { 897 | PX_SCHED_TRACE_FN("Init"); 898 | stop(); 899 | running_.store(true); 900 | params_ = _params; 901 | if (params_.max_running_threads == 0) { 902 | params_.max_running_threads = static_cast(std::thread::hardware_concurrency()); 903 | } 904 | // create tasks 905 | tasks_.init(params_.max_number_tasks, params_.mem_callbacks); 906 | counters_.init(params_.max_number_tasks, params_.mem_callbacks); 907 | ready_tasks_.init(params_.max_number_tasks, params_.mem_callbacks); 908 | PX_SCHED_CHECK_FN(workers_ == nullptr, "workers_ ptr should be null here..."); 909 | workers_ = static_cast(params_.mem_callbacks.alloc_fn(alignof(Worker), sizeof(Worker)*params_.num_threads)); 910 | for(uint16_t i = 0; i < params_.num_threads; ++i) { 911 | new (&workers_[i]) Worker(); 912 | workers_[i].thread_index = i; 913 | } 914 | PX_SCHED_CHECK_FN(active_threads_.load() == 0, "Invalid active threads num"); 915 | for(uint16_t i = 0; i < params_.num_threads; ++i) { 916 | workers_[i].thread = std::thread(WorkerThreadMain, this, &workers_[i]); 917 | } 918 | } 919 | 920 | void Scheduler::stop() { 921 | PX_SCHED_TRACE_FN("Stop"); 922 | if (running_.load()) { 923 | running_.store(false); 924 | for(uint16_t i = 0; i < params_.num_threads; ++i) { 925 | wakeUpThreads(params_.num_threads); 926 | } 927 | for(uint16_t i = 0; i < params_.num_threads; ++i) { 928 | workers_[i].thread.join(); 929 | workers_[i].~Worker(); 930 | } 931 | params_.mem_callbacks.free_fn(workers_); 932 | workers_ = nullptr; 933 | tasks_.reset(); 934 | counters_.reset(); 935 | ready_tasks_.reset(); 936 | PX_SCHED_CHECK_FN(active_threads_.load() == 0, "Invalid active threads num --> %u", active_threads_.load()); 937 | } 938 | } 939 | 940 | void Scheduler::getDebugStatus(char *buffer, size_t buffer_size) { 941 | PX_SCHED_TRACE_FN("GetDebugStatus"); 942 | size_t p = 0; 943 | int n = 0; 944 | #define _ADD(...) {p += static_cast(n); (p < buffer_size) && (n = snprintf(buffer+p, buffer_size-p,__VA_ARGS__));} 945 | _ADD("Workers:0 5 10 15 20 25 30 35 40 45 50 55 60 65 70 75\n"); 946 | _ADD("%3u/%3u:", active_threads_.load(), params_.max_running_threads); 947 | for(size_t i = 0; i < params_.num_threads; ++i) { 948 | _ADD( (workers_[i].wake_up.load() == nullptr)?"*":"."); 949 | } 950 | _ADD("\nWorkers(%d):", params_.num_threads); 951 | for(size_t i = 0; i < params_.num_threads; ++i) { 952 | auto &w = workers_[i]; 953 | bool is_on =(w.wake_up.load() == nullptr); 954 | if (!is_on) { 955 | continue; 956 | } 957 | _ADD("\n Worker: %d(%s) %s", w.thread_index, 958 | is_on?"ON":"OFF", 959 | w.thread_tls->name? w.thread_tls->name: "-no-name-" 960 | ); 961 | } 962 | _ADD("\nReady: "); 963 | for(uint32_t i = 0; i < ready_tasks_.in_use_; ++i) { 964 | _ADD("%d,",ready_tasks_.list_[i]); 965 | } 966 | _ADD("\nTasks: "); 967 | for(uint32_t i = 0; i < tasks_.size(); ++i) { 968 | uint32_t c,v; 969 | uint32_t hnd = tasks_.info(i, &c, &v); 970 | if (c>0) { _ADD("%u,",hnd); } 971 | } 972 | _ADD("\nCounters:"); 973 | for(uint32_t i = 0; i < counters_.size(); ++i) { 974 | uint32_t c,v; 975 | uint32_t hnd = counters_.info(i, &c, &v); 976 | if (c>0) { _ADD("%u,",hnd); } 977 | } 978 | _ADD("\n"); 979 | #undef _ADD 980 | } 981 | 982 | uint16_t Scheduler::wakeUpThreads(uint16_t max_num_threads) { 983 | //PX_SCHED_TRACE_FN("WakeUpThreads"); 984 | uint16_t total_woken_up = 0; 985 | for(uint32_t i = 0; (i < params_.num_threads) && (total_woken_up < max_num_threads); ++i) { 986 | WaitFor *wake_up = workers_[i].wake_up.exchange(nullptr); 987 | if (wake_up) { 988 | wake_up->signal(); 989 | total_woken_up++; 990 | // Add one to the total active threads, for later substracting it, this 991 | // will take the thread as awake before the thread actually is again working 992 | active_threads_.fetch_add(1); 993 | } 994 | } 995 | active_threads_.fetch_sub(total_woken_up); 996 | return total_woken_up; 997 | } 998 | 999 | void Scheduler::wakeUpOneThread() { 1000 | PX_SCHED_TRACE_FN("WakeUpOneThread"); 1001 | // TODO: Investigate this, there is a situation where no matter how much we wait 1002 | // it is unable to wakeup a single thread (Emscripten -> C++) 1003 | for(int tries = 0; tries < 1; ++tries) { 1004 | uint32_t active = active_threads_.load(); 1005 | if ((active >= params_.max_running_threads) || 1006 | wakeUpThreads(1)) return; 1007 | // wait a bit... 1008 | std::this_thread::yield(); 1009 | } 1010 | } 1011 | 1012 | void Scheduler::run(Job &&job, Sync *sync_obj) { 1013 | PX_SCHED_TRACE_FN("RunTask"); 1014 | PX_SCHED_CHECK_FN(running_.load(), "Scheduler not running"); 1015 | uint32_t t_ref = createTask(std::move(job), sync_obj); 1016 | ready_tasks_.push(t_ref); 1017 | wakeUpOneThread(); 1018 | } 1019 | 1020 | void Scheduler::runAfter(Sync _trigger, Job&& _job, Sync* _sync_obj) { 1021 | PX_SCHED_TRACE_FN("RunTaskAfter"); 1022 | PX_SCHED_CHECK_FN(running_.load(), "Scheduler not running"); 1023 | uint32_t trigger = _trigger.hnd; 1024 | uint32_t t_ref = createTask(std::move(_job), _sync_obj); 1025 | bool valid = counters_.ref(trigger); 1026 | if (valid) { 1027 | Counter *c = &counters_.get(trigger); 1028 | for(;;) { 1029 | uint32_t current = c->task_id.load(); 1030 | if (c->task_id.compare_exchange_strong(current, t_ref)) { 1031 | Task *task = &tasks_.get(t_ref); 1032 | task->next_sibling_task.store(current); 1033 | break; 1034 | } 1035 | } 1036 | unrefCounter(trigger); 1037 | } else { 1038 | ready_tasks_.push(t_ref); 1039 | wakeUpOneThread(); 1040 | } 1041 | } 1042 | 1043 | void Scheduler::waitFor(Sync s) { 1044 | PX_SCHED_TRACE_FN("WaitFor"); 1045 | if (counters_.ref(s.hnd)) { 1046 | Counter &counter = counters_.get(s.hnd); 1047 | PX_SCHED_CHECK_FN(counter.wait_ptr == nullptr, "Sync object already used for waitFor operation, only one is permitted"); 1048 | WaitFor wf; 1049 | counter.wait_ptr = &wf; 1050 | unrefCounter(s.hnd); 1051 | wf.wait(); 1052 | } 1053 | } 1054 | 1055 | uint32_t Scheduler::numPendingTasks(Sync s) { 1056 | return counters_.refCount(s.hnd); 1057 | } 1058 | 1059 | void Scheduler::unrefCounter(uint32_t hnd) { 1060 | PX_SCHED_TRACE_FN("UnrefCounter"); 1061 | if (counters_.ref(hnd)) { 1062 | counters_.unref(hnd); 1063 | Scheduler *schd = this; 1064 | counters_.unref(hnd, [schd](Counter &c) { 1065 | // wake up all tasks 1066 | uint32_t tid = c.task_id.load(); 1067 | while (schd->tasks_.ref(tid)) { 1068 | Task &task = schd->tasks_.get(tid); 1069 | uint32_t next_tid = task.next_sibling_task.load(); 1070 | task.next_sibling_task.store(0); 1071 | schd->ready_tasks_.push(tid); 1072 | schd->wakeUpOneThread(); 1073 | schd->tasks_.unref(tid); 1074 | tid = next_tid; 1075 | } 1076 | if (c.wait_ptr) { 1077 | c.wait_ptr->signal(); 1078 | } 1079 | }); 1080 | } 1081 | } 1082 | 1083 | void Scheduler::WorkerThreadMain(Scheduler *schd, Scheduler::Worker *worker_data) { 1084 | char buffer[16]; 1085 | 1086 | const uint16_t id = worker_data->thread_index; 1087 | TLS *local_storage = tls(); 1088 | 1089 | local_storage->scheduler = schd; 1090 | worker_data->thread_tls = local_storage; 1091 | 1092 | auto const ttl_wait = schd->params_.thread_sleep_on_idle_in_microseconds; 1093 | auto const ttl_value = schd->params_.thread_num_tries_on_idle? schd->params_.thread_num_tries_on_idle:1; 1094 | schd->active_threads_.fetch_add(1); 1095 | snprintf(buffer,16,"Worker-%u", id); 1096 | schd->set_current_thread_name(buffer); 1097 | for(;;) { 1098 | { // wait for new activity 1099 | PX_SCHED_TRACE_FN("WorkerGoToSleep"); 1100 | auto current_num = schd->active_threads_.fetch_sub(1); 1101 | if (!schd->running_.load()) return; 1102 | if (schd->ready_tasks_.in_use() == 0 || 1103 | current_num > schd->params_.max_running_threads) { 1104 | WaitFor wf; 1105 | schd->workers_[id].wake_up.store(&wf); 1106 | wf.wait(); 1107 | if (!schd->running_.load()) return; 1108 | } 1109 | schd->active_threads_.fetch_add(1); 1110 | schd->workers_[id].wake_up.store(nullptr); 1111 | } 1112 | auto ttl = ttl_value; 1113 | { // do some work 1114 | PX_SCHED_TRACE_FN("WorkerRunning"); 1115 | uint32_t task_ref; 1116 | while (ttl && schd->running_.load()) { 1117 | if (!schd->ready_tasks_.pop(&task_ref)) { 1118 | PX_SCHED_TRACE_FN("No Task->sleep"); 1119 | ttl--; 1120 | if (ttl_wait) std::this_thread::sleep_for(std::chrono::microseconds(ttl_wait)); 1121 | continue; 1122 | } 1123 | ttl = ttl_value; 1124 | Task *t = &schd->tasks_.get(task_ref); 1125 | t->job(); 1126 | uint32_t counter = t->counter_id; 1127 | schd->tasks_.unref(task_ref); 1128 | schd->unrefCounter(counter); 1129 | } 1130 | } 1131 | } 1132 | worker_data->thread_tls = nullptr; 1133 | local_storage->scheduler = nullptr; 1134 | schd->set_current_thread_name(nullptr); 1135 | } 1136 | } // end of px_sched namespace 1137 | #endif // PX_SCHED_IMP_REGULAR_THREADS 1138 | 1139 | #endif // PX_SCHED_IMPLEMENTATION_DONE 1140 | 1141 | #endif // PX_SCHED_IMPLEMENTATION 1142 | --------------------------------------------------------------------------------