diff --git a/CMakeLists.txt b/CMakeLists.txt index 61ed54e906..67f076d618 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -274,8 +274,12 @@ endif() set (FILAMENT_SAMPLES_BINARY_DIR ${PROJECT_BINARY_DIR}/samples) if (WEBGL) - add_subdirectory(${EXTERNAL}/stb/tnt) add_subdirectory(${FILAMENT}/samples/web) + + add_subdirectory(${LIBRARIES}/filagui) + + add_subdirectory(${EXTERNAL}/imgui/tnt) + add_subdirectory(${EXTERNAL}/stb/tnt) endif() if (NOT ANDROID AND NOT WEBGL) diff --git a/libs/filagui/CMakeLists.txt b/libs/filagui/CMakeLists.txt index 7189581c4e..81526538e7 100644 --- a/libs/filagui/CMakeLists.txt +++ b/libs/filagui/CMakeLists.txt @@ -29,7 +29,7 @@ set(GENERATION_ROOT ${CMAKE_CURRENT_BINARY_DIR}) file(MAKE_DIRECTORY "${GENERATION_ROOT}/generated/material/") # Target system. -if (ANDROID) +if (ANDROID OR WEBGL) set(MATC_TARGET mobile) else() set(MATC_TARGET desktop) diff --git a/libs/filagui/include/filagui/ImGuiHelper.h b/libs/filagui/include/filagui/ImGuiHelper.h index 022773822d..09973c1dfc 100644 --- a/libs/filagui/include/filagui/ImGuiHelper.h +++ b/libs/filagui/include/filagui/ImGuiHelper.h @@ -48,6 +48,7 @@ public: ~ImGuiHelper(); // Informs ImGui of the current display size, as well as the pixel ratio for high DPI displays. + // The display size is given in terms of virtual pixels, not physical pixels. void setDisplaySize(int width, int height, float scaleX = 0.0f, float scaleY = 0.0f); // This does not actually "render" in the sense of issuing OpenGL commands, diff --git a/libs/filagui/src/ImGuiHelper.cpp b/libs/filagui/src/ImGuiHelper.cpp index b2eb5edbc3..f4807ee4f6 100644 --- a/libs/filagui/src/ImGuiHelper.cpp +++ b/libs/filagui/src/ImGuiHelper.cpp @@ -49,7 +49,9 @@ ImGuiHelper::ImGuiHelper(Engine* engine, filament::View* view, const Path& fontP // If the given font path is invalid, ImGui will silently fall back to proggy, which is a // tiny "pixel art" texture that is compiled into the library. - io.Fonts->AddFontFromFileTTF(fontPath.c_str(), 16.0f); + if (!fontPath.isEmpty()) { + io.Fonts->AddFontFromFileTTF(fontPath.c_str(), 16.0f); + } // Create the grayscale texture that ImGui uses for its glyph atlas. static unsigned char* pixels; @@ -306,12 +308,14 @@ void ImGuiHelper::populateVertexData(size_t bufferIndex, size_t vbSizeInBytes, v } void ImGuiHelper::syncThreads() { +#if UTILS_HAS_THREADING if (!mHasSynced) { // This is called only when ImGui needs to grow a vertex buffer, which occurs a few times // after launching and rarely (if ever) after that. Fence::waitAndDestroy(mEngine->createFence()); mHasSynced = true; } +#endif } } // namespace filagui diff --git a/samples/web/CMakeLists.txt b/samples/web/CMakeLists.txt index ea53178741..793fae8af3 100644 --- a/samples/web/CMakeLists.txt +++ b/samples/web/CMakeLists.txt @@ -65,35 +65,42 @@ copy_assets("." filaweb.js) copy_assets("." favicon.png) copy_assets("../../assets/models" monkey) -# Convert OBJ into a simple binary format. -set(source_mesh "${CMAKE_CURRENT_SOURCE_DIR}/../../assets/models/monkey/monkey.obj") -set(target_mesh "public/monkey/mesh.filamesh") -add_custom_command( - OUTPUT ${target_mesh} - COMMAND filamesh ${source_mesh} public/monkey/mesh.filamesh - MAIN_DEPENDENCY ${source_mesh} - DEPENDS filamesh) +# Convert OBJ files into filamesh files. +function(add_mesh SOURCE TARGET) + set(source_mesh "${CMAKE_CURRENT_SOURCE_DIR}/../../${SOURCE}") + set(target_mesh "public/${TARGET}") + set(target_meshes ${target_meshes} ${target_mesh} PARENT_SCOPE) + add_custom_command( + OUTPUT ${target_mesh} + COMMAND filamesh ${source_mesh} ${target_mesh} + MAIN_DEPENDENCY ${source_mesh} + DEPENDS filamesh) +endfunction() -# Next build two cubemaps: -# (1) single miplevel of blurry skybox -# (2) all miplevels for the IBL +add_mesh("assets/models/monkey/monkey.obj" "monkey/mesh.filamesh") +add_mesh("third_party/shader_ball/shader_ball.obj" "shaderball/mesh.filamesh") -set(source_envmap "${CMAKE_CURRENT_SOURCE_DIR}/../../third_party/environments/syferfontein_18d_clear_2k.hdr") - -foreach(FACE nx ny nz px py pz) - set(target_skybox ${target_skybox} public/syferfontein_18d_clear_2k/${FACE}.rgbm) - foreach(MIP 0 1 2 3 4 5 6 7 8) - set(target_envmap ${target_envmap} public/syferfontein_18d_clear_2k/m${MIP}_${FACE}.rgbm) +function(add_envmap SOURCE TARGET) + set(source_envmap "${CMAKE_CURRENT_SOURCE_DIR}/../../${SOURCE}") + foreach(FACE nx ny nz px py pz) + set(target_skybox ${target_skybox} ${TARGET}/${FACE}.rgbm) + foreach(MIP 0 1 2 3 4 5 6 7 8) + set(target_envmap ${target_envmap} ${TARGET}/m${MIP}_${FACE}.rgbm) + endforeach() endforeach() -endforeach() + set(target_skyboxes ${target_skyboxes} ${target_skybox} PARENT_SCOPE) + add_custom_command( + OUTPUT ${target_skybox} ${target_envmap} + COMMAND cmgen -x public --format=rgbm --size=512 --extract-blur=0.1 ${source_envmap} + MAIN_DEPENDENCY ${source_envmap} + DEPENDS cmgen) + set(target_envmaps ${target_envmaps} ${target_envmap} PARENT_SCOPE) +endfunction() -add_custom_command( - OUTPUT ${target_skybox} ${target_envmap} - COMMAND cmgen -x public --format=rgbm --size=512 --extract-blur=0.1 ${source_envmap} - MAIN_DEPENDENCY ${source_envmap} - DEPENDS cmgen) +add_envmap("third_party/environments/syferfontein_18d_clear_2k.hdr" "public/syferfontein_18d_clear_2k") +add_envmap("third_party/environments/pillars_2k.hdr" "public/pillars_2k") -add_custom_target(sample_assets DEPENDS ${target_mesh} ${target_envmap} ${target_skybox}) +add_custom_target(sample_assets DEPENDS ${target_meshes} ${target_envmaps} ${target_skyboxes}) # ================================================================================================== # For each demo, build a pair of wasm/js files and copy over an HTML file. @@ -118,7 +125,7 @@ function(add_demo NAME) filaweb.h filaweb.cpp) add_dependencies(${NAME} sample_materials sample_assets) - target_link_libraries(${NAME} PRIVATE filament math utils stb) + target_link_libraries(${NAME} PRIVATE filament filagui math utils stb) # Copy the generated js and wasm files into the public folder, as well as the app-specific # manually-written HTML file. diff --git a/samples/web/filaweb.cpp b/samples/web/filaweb.cpp index 84fb0020c1..89f43fa46a 100644 --- a/samples/web/filaweb.cpp +++ b/samples/web/filaweb.cpp @@ -31,8 +31,8 @@ extern "C" void render() { filaweb::Application::get()->render(); } -extern "C" void resize(uint32_t width, uint32_t height) { - filaweb::Application::get()->resize(width, height); +extern "C" void resize(uint32_t width, uint32_t height, double pixelRatio) { + filaweb::Application::get()->resize(width, height, pixelRatio); } namespace filaweb { @@ -142,8 +142,7 @@ SkyLight getSkyLight(Engine& engine, const char* name) { // Pull the data out of JavaScript. static auto asset = filaweb::getCubemap(name); - printf("syferfontein_18d_clear_2k: %d x %d, %d mips\n", - asset.width, asset.height, asset.envMipCount); + printf("%s: %d x %d, %d mips\n", name, asset.width, asset.height, asset.envMipCount); // Parse the coefficients. std::istringstream shReader((const char*) asset.envShCoeffs->data.get()); diff --git a/samples/web/filaweb.h b/samples/web/filaweb.h index a02bd2efb8..d53eb9fe10 100644 --- a/samples/web/filaweb.h +++ b/samples/web/filaweb.h @@ -17,6 +17,9 @@ #ifndef TNT_FILAMENT_SAMPLES_FILAWEB_H #define TNT_FILAMENT_SAMPLES_FILAWEB_H +#include + +#include #include #include #include @@ -59,6 +62,8 @@ struct SkyLight { SkyLight getSkyLight(filament::Engine& engine, const char* name); +static const auto NoopCallback = [](filament::Engine*, filament::View*) {}; + class Application { public: using Engine = filament::Engine; @@ -73,19 +78,35 @@ public: return &app; } - void run(SetupCallback setup, ImGuiCallback imgui, AnimCallback animation) { + void run(SetupCallback setup, AnimCallback animation, ImGuiCallback imgui = NoopCallback) { mAnimation = animation; + mGuiCallback = imgui; mEngine = Engine::create(Engine::Backend::OPENGL); mSwapChain = mEngine->createSwapChain(nullptr); mScene = mEngine->createScene(); mRenderer = mEngine->createRenderer(); mView = mEngine->createView(); mView->setScene(mScene); + mGuiCam = mEngine->createCamera(); + mGuiView = mEngine->createView(); + mGuiView->setClearTargets(false, false, false); + mGuiView->setRenderTarget(View::TargetBufferFlags::DEPTH_AND_STENCIL); + mGuiView->setPostProcessingEnabled(false); + mGuiView->setShadowsEnabled(false); + mGuiView->setCamera(mGuiCam); + mGuiHelper = new filagui::ImGuiHelper(mEngine, mGuiView, ""); setup(mEngine, mView, mScene); } - void resize(uint32_t width, uint32_t height) { + void resize(uint32_t width, uint32_t height, double pixelRatio) { + mPixelRatio = pixelRatio; mView->setViewport({0, 0, width, height}); + mGuiView->setViewport({0, 0, width, height}); + mGuiCam->setProjection(filament::Camera::Projection::ORTHO, + 0.0, width / pixelRatio, + height / pixelRatio, 0.0, + 0.0, 1.0); + mGuiHelper->setDisplaySize(width / pixelRatio, height / pixelRatio, pixelRatio, pixelRatio); } void render() { @@ -93,8 +114,16 @@ public: std::chrono::system_clock::now().time_since_epoch() / std::chrono::milliseconds(1); mAnimation(mEngine, mView, milliseconds_since_epoch / 1000.0); + mGuiCallback(mEngine, mView); + + double now = milliseconds_since_epoch / 1000.0; + static double previous = now; + mGuiHelper->render(now - previous, mGuiCallback); + previous = now; + if (mRenderer->beginFrame(mSwapChain)) { mRenderer->render(mView); + mRenderer->render(mGuiView); mRenderer->endFrame(); } mEngine->execute(); @@ -102,12 +131,17 @@ public: private: Application() { } + filagui::ImGuiHelper* mGuiHelper = nullptr; Engine* mEngine = nullptr; Scene* mScene = nullptr; View* mView = nullptr; + View* mGuiView = nullptr; + filament::Camera* mGuiCam = nullptr; filament::Renderer* mRenderer = nullptr; filament::SwapChain* mSwapChain = nullptr; AnimCallback mAnimation; + ImGuiCallback mGuiCallback; + double mPixelRatio = 1.0; }; } // namespace filaweb diff --git a/samples/web/filaweb.js b/samples/web/filaweb.js index f2a9d6e1af..c677d91612 100644 --- a/samples/web/filaweb.js +++ b/samples/web/filaweb.js @@ -50,7 +50,7 @@ function canvas_resize() { let h = window.innerHeight; canvas.width = (w * pr) | 0; canvas.height = (h * pr) | 0; - _resize((w * pr) | 0, (h * pr) | 0 ); + _resize((w * pr) | 0, (h * pr) | 0, pr); } function load_rawfile(url) { diff --git a/samples/web/suzanne.cpp b/samples/web/suzanne.cpp index a72fdcbc0c..93795cbcd0 100644 --- a/samples/web/suzanne.cpp +++ b/samples/web/suzanne.cpp @@ -170,7 +170,7 @@ void setup(Engine* engine, View* view, Scene* scene) { scene->addEntity(app.ptlight[2]); scene->addEntity(app.ptlight[3]); - // Create sky box and image-based light source. + // Create skybox and image-based light source. auto skylight = filaweb::getSkyLight(*engine, "syferfontein_18d_clear_2k"); scene->setIndirectLight(skylight.indirectLight); scene->setSkybox(skylight.skybox); @@ -194,13 +194,10 @@ void animate(Engine* engine, View* view, double now) { mat4f::rotate(now, math::float3{0, 1, 0})); }; -void gui(filament::Engine* engine, filament::View*) { -}; - // This is called only after the JavaScript layer has created a WebGL 2.0 context and all assets // have been downloaded. extern "C" void launch() { - filaweb::Application::get()->run(setup, gui, animate); + filaweb::Application::get()->run(setup, animate); } // The main() entry point is implicitly called after JIT compilation, but potentially before the diff --git a/samples/web/triangle.cpp b/samples/web/triangle.cpp index 675a85571f..33ce6eb6f9 100644 --- a/samples/web/triangle.cpp +++ b/samples/web/triangle.cpp @@ -29,7 +29,6 @@ #include using namespace filament; -using namespace std; using utils::Entity; using utils::EntityManager; @@ -111,13 +110,10 @@ void animate(Engine* engine, View* view, double now) { math::mat4f::rotate(now, math::float3{0, 0, 1})); }; -void gui(filament::Engine* engine, filament::View*) { -}; - // This is called only after the JavaScript layer has created a WebGL 2.0 context and all assets // have been downloaded. extern "C" void launch() { - filaweb::Application::get()->run(setup, gui, animate); + filaweb::Application::get()->run(setup, animate); } // The main() entry point is implicitly called after JIT compilation, but potentially before the