From 8d8760b6f851898f2fb54a3dcab8002b1d96d41a Mon Sep 17 00:00:00 2001 From: Victor Stanchevici Date: Thu, 14 Mar 2024 19:48:12 +0200 Subject: [PATCH 01/15] Adding support for emscripten --- CMakeLists.txt | 16 +- cmake/FindOpenGLES3.cmake | 9 + .../Emscripten/EmscriptenNativeHandle.h | 39 ++ include/LLGL/Backend/OpenGL/NativeHandle.h | 2 + .../Emscripten/EmscriptenNativeHandle.h | 32 ++ include/LLGL/Platform/NativeHandle.h | 2 + include/LLGL/Platform/Platform.h | 2 + sources/Platform/Debug.h | 2 + .../Platform/Emscripten/EmscriptenDebug.cpp | 26 + sources/Platform/Emscripten/EmscriptenDebug.h | 25 + .../Platform/Emscripten/EmscriptenDisplay.cpp | 174 ++++++ .../Platform/Emscripten/EmscriptenDisplay.h | 60 +++ .../Platform/Emscripten/EmscriptenModule.cpp | 112 ++++ .../Platform/Emscripten/EmscriptenModule.h | 50 ++ .../Platform/Emscripten/EmscriptenPath.cpp | 42 ++ .../Platform/Emscripten/EmscriptenTimer.cpp | 45 ++ .../Platform/Emscripten/EmscriptenWindow.cpp | 501 ++++++++++++++++++ .../Platform/Emscripten/EmscriptenWindow.h | 89 ++++ sources/Platform/Emscripten/MapKey.cpp | 202 +++++++ sources/Platform/Emscripten/MapKey.h | 30 ++ sources/Renderer/OpenGL/CMakeLists.txt | 13 +- .../OpenGL/GLCoreProfile/OpenGLCore.h | 2 + .../Renderer/OpenGL/GLESProfile/OpenGLES.h | 5 + .../Emscripten/EmscriptenGLContext.cpp | 170 ++++++ .../Platform/Emscripten/EmscriptenGLContext.h | 89 ++++ .../EmscriptenGLSwapChainContext.cpp | 81 +++ .../Emscripten/EmscriptenGLSwapChainContext.h | 55 ++ 27 files changed, 1869 insertions(+), 6 deletions(-) create mode 100644 include/LLGL/Backend/OpenGL/Emscripten/EmscriptenNativeHandle.h create mode 100644 include/LLGL/Platform/Emscripten/EmscriptenNativeHandle.h create mode 100644 sources/Platform/Emscripten/EmscriptenDebug.cpp create mode 100644 sources/Platform/Emscripten/EmscriptenDebug.h create mode 100644 sources/Platform/Emscripten/EmscriptenDisplay.cpp create mode 100644 sources/Platform/Emscripten/EmscriptenDisplay.h create mode 100644 sources/Platform/Emscripten/EmscriptenModule.cpp create mode 100644 sources/Platform/Emscripten/EmscriptenModule.h create mode 100644 sources/Platform/Emscripten/EmscriptenPath.cpp create mode 100644 sources/Platform/Emscripten/EmscriptenTimer.cpp create mode 100644 sources/Platform/Emscripten/EmscriptenWindow.cpp create mode 100644 sources/Platform/Emscripten/EmscriptenWindow.h create mode 100644 sources/Platform/Emscripten/MapKey.cpp create mode 100644 sources/Platform/Emscripten/MapKey.h create mode 100644 sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.cpp create mode 100644 sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.h create mode 100644 sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.cpp create mode 100644 sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ebb4953aaf..f6a815e274 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,8 @@ if(NOT DEFINED LLGL_TARGET_PLATFORM) elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "WindowsStore") set(LLGL_TARGET_PLATFORM "${CMAKE_SYSTEM_NAME} ${CMAKE_SYSTEM_VERSION}") set(LLGL_UWP_PLATFORM ON) + elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Emscripten") + set(LLGL_TARGET_PLATFORM "Emscripten") elseif(WIN32) if(LLGL_BUILD_64BIT) set(LLGL_TARGET_PLATFORM "Win64") @@ -408,7 +410,7 @@ option(LLGL_BUILD_EXAMPLES "Include example projects" OFF) option(LLGL_BUILD_RENDERER_NULL "Include Null renderer project" ON) if(NOT LLGL_UWP_PLATFORM) - if(LLGL_MOBILE_PLATFORM) + if(LLGL_MOBILE_PLATFORM OR EMSCRIPTEN) option(LLGL_BUILD_RENDERER_OPENGLES3 "Include OpenGLES 3 renderer project" ON) else() option(LLGL_BUILD_RENDERER_OPENGL "Include OpenGL renderer project" ON) @@ -483,6 +485,11 @@ else() set(SUMMARY_TARGET_ARCH "x86") endif() +if(EMSCRIPTEN) + add_compile_options("SHELL:-s USE_PTHREADS") + add_link_options("SHELL:-s USE_PTHREADS") +endif() + # === Global files === @@ -522,7 +529,10 @@ if(LLGL_ENABLE_DEBUG_LAYER) find_source_files(FilesRendererDbgTexture CXX "${PROJECT_SOURCE_DIR}/sources/Renderer/DebugLayer/Texture") endif() -if(WIN32) +if(EMSCRIPTEN) + find_source_files(FilesPlatform CXX "${PROJECT_SOURCE_DIR}/sources/Platform/Emscripten") + find_source_files(FilesIncludePlatform CXX "${PROJECT_INCLUDE_DIR}/LLGL/Platform/Emscripten") +elseif(WIN32) if(LLGL_UWP_PLATFORM) find_source_files(FilesPlatform CXX "${PROJECT_SOURCE_DIR}/sources/Platform/UWP") find_source_files(FilesIncludePlatform CXX "${PROJECT_INCLUDE_DIR}/LLGL/Platform/UWP") @@ -679,6 +689,8 @@ elseif(APPLE) if(LLGL_MACOS_ENABLE_COREVIDEO) target_link_libraries(LLGL "-framework CoreVideo") endif() +elseif(EMSCRIPTEN) + ### elseif(UNIX) target_link_libraries(LLGL X11 pthread Xrandr) #elseif(LLGL_UWP_PLATFORM) diff --git a/cmake/FindOpenGLES3.cmake b/cmake/FindOpenGLES3.cmake index 87621d2e31..a40779fcf6 100644 --- a/cmake/FindOpenGLES3.cmake +++ b/cmake/FindOpenGLES3.cmake @@ -206,6 +206,15 @@ ELSE (WIN32) findpkg_framework(OpenGLES) set(OPENGLES_gl_LIBRARY "-framework OpenGLES") + ELSEIF(EMSCRIPTEN) + + find_package(OpenGL REQUIRED) + #message(STATUS "OPENGL_INCLUDE_DIR: " ${OPENGL_INCLUDE_DIR}) + #message(STATUS "OPENGL_LIBRARIES: " ${OPENGL_LIBRARIES}) + + SET(OPENGLES_INCLUDE_DIR ${OPENGL_INCLUDE_DIR}) + SET(OPENGLES_gl_LIBRARY ${OPENGL_LIBRARIES}) + ELSE(APPLE) FIND_PATH(OPENGLES_INCLUDE_DIR GLES3/gl3.h diff --git a/include/LLGL/Backend/OpenGL/Emscripten/EmscriptenNativeHandle.h b/include/LLGL/Backend/OpenGL/Emscripten/EmscriptenNativeHandle.h new file mode 100644 index 0000000000..892aa898e1 --- /dev/null +++ b/include/LLGL/Backend/OpenGL/Emscripten/EmscriptenNativeHandle.h @@ -0,0 +1,39 @@ +/* + * EmscriptenNativeHandle.h (OpenGL) + * + * Copyright (c) 2015 Lukas Hermanns. All rights reserved. + * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). + */ + +#ifndef LLGL_OPENGL_EMSCRIPTEN_NATIVE_HANDLE_H +#define LLGL_OPENGL_EMSCRIPTEN_NATIVE_HANDLE_H + + +namespace LLGL +{ + +namespace OpenGL +{ + + +/** +\brief Emscripten native handle structure for the OpenGL render system. +\see RenderSystem::GetNativeHandle +\see RenderSystemDescriptor::nativeHandle +*/ +struct RenderSystemNativeHandle +{ + int context; +}; + + +} // /namespace OpenGL + +} // /namespace LLGL + + +#endif + + + +// ================================================================================ diff --git a/include/LLGL/Backend/OpenGL/NativeHandle.h b/include/LLGL/Backend/OpenGL/NativeHandle.h index cb9582599d..8892113139 100644 --- a/include/LLGL/Backend/OpenGL/NativeHandle.h +++ b/include/LLGL/Backend/OpenGL/NativeHandle.h @@ -21,6 +21,8 @@ # include #elif defined(LLGL_OS_ANDROID) # include +#elif defined(LLGL_OS_EMSCRIPTEN) +# include #endif diff --git a/include/LLGL/Platform/Emscripten/EmscriptenNativeHandle.h b/include/LLGL/Platform/Emscripten/EmscriptenNativeHandle.h new file mode 100644 index 0000000000..1df80371f4 --- /dev/null +++ b/include/LLGL/Platform/Emscripten/EmscriptenNativeHandle.h @@ -0,0 +1,32 @@ +/* + * LinuxNativeHandle.h + * + * Copyright (c) 2015 Lukas Hermanns. All rights reserved. + * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). + */ + +#ifndef LLGL_EMSCRIPTEN_NATIVE_HANDLE_H +#define LLGL_EMSCRIPTEN_NATIVE_HANDLE_H + +#include + +namespace LLGL +{ + + +//! Emscripten native handle structure. +struct NativeHandle +{ + //! HTML canvas object. + emscripten::val canvas; +}; + + +} // /namespace LLGL + + +#endif + + + +// ================================================================================ diff --git a/include/LLGL/Platform/NativeHandle.h b/include/LLGL/Platform/NativeHandle.h index 8564869d75..c0099f96eb 100644 --- a/include/LLGL/Platform/NativeHandle.h +++ b/include/LLGL/Platform/NativeHandle.h @@ -19,6 +19,8 @@ # include #elif defined(LLGL_OS_LINUX) # include +#elif defined(LLGL_OS_EMSCRIPTEN) +# include #elif defined(LLGL_OS_IOS) # include #elif defined(LLGL_OS_ANDROID) diff --git a/include/LLGL/Platform/Platform.h b/include/LLGL/Platform/Platform.h index 30a4989d91..45313fd5b2 100644 --- a/include/LLGL/Platform/Platform.h +++ b/include/LLGL/Platform/Platform.h @@ -30,6 +30,8 @@ see https://sourceforge.net/p/predef/wiki/OperatingSystems/ # endif #elif defined __ANDROID__ || defined ANDROID # define LLGL_OS_ANDROID +#elif defined EMSCRIPTEN +# define LLGL_OS_EMSCRIPTEN #elif defined __linux__ # define LLGL_OS_LINUX #endif diff --git a/sources/Platform/Debug.h b/sources/Platform/Debug.h index c1ff5262c3..e6be12d3ba 100644 --- a/sources/Platform/Debug.h +++ b/sources/Platform/Debug.h @@ -22,6 +22,8 @@ # include "MacOS/MacOSDebug.h" # elif defined(LLGL_OS_LINUX) # include "Linux/LinuxDebug.h" +# elif defined(LLGL_OS_EMSCRIPTEN) +# include "Emscripten/EmscriptenDebug.h" # elif defined(LLGL_OS_IOS) # include "IOS/IOSDebug.h" # elif defined(LLGL_OS_ANDROID) diff --git a/sources/Platform/Emscripten/EmscriptenDebug.cpp b/sources/Platform/Emscripten/EmscriptenDebug.cpp new file mode 100644 index 0000000000..2655a054f6 --- /dev/null +++ b/sources/Platform/Emscripten/EmscriptenDebug.cpp @@ -0,0 +1,26 @@ +/* + * LinuxDebug.cpp + * + * Copyright (c) 2015 Lukas Hermanns. All rights reserved. + * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). + */ + +#include "../Debug.h" +#include + + +namespace LLGL +{ + + +LLGL_EXPORT void DebugPuts(const char* text) +{ + ::fprintf(stderr, "%s\n", text); +} + + +} // /namespace LLGL + + + +// ================================================================================ diff --git a/sources/Platform/Emscripten/EmscriptenDebug.h b/sources/Platform/Emscripten/EmscriptenDebug.h new file mode 100644 index 0000000000..52fc4062e8 --- /dev/null +++ b/sources/Platform/Emscripten/EmscriptenDebug.h @@ -0,0 +1,25 @@ +/* + * LinuxDebug.h + * + * Copyright (c) 2015 Lukas Hermanns. All rights reserved. + * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). + */ + +#ifndef LLGL_EMSCRIPTEN_DEBUG_H +#define LLGL_EMSCRIPTEN_DEBUG_H + + +#include + +#ifdef SIGTRAP +# define LLGL_DEBUG_BREAK() raise(SIGTRAP) +#else +# define LLGL_DEBUG_BREAK() +#endif + + +#endif + + + +// ================================================================================ diff --git a/sources/Platform/Emscripten/EmscriptenDisplay.cpp b/sources/Platform/Emscripten/EmscriptenDisplay.cpp new file mode 100644 index 0000000000..4af200c43f --- /dev/null +++ b/sources/Platform/Emscripten/EmscriptenDisplay.cpp @@ -0,0 +1,174 @@ +/* + * LinuxDisplay.h + * + * Copyright (c) 2015 Lukas Hermanns. All rights reserved. + * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). + */ + +#include "EmscriptenDisplay.h" +#include "../../Core/CoreUtils.h" + + +namespace LLGL +{ + + +/* + * Display class + */ + +std::size_t Display::Count() +{ + return 0; +} + +Display* const * Display::GetList() +{ + /* + if (UpdateDisplayList() || g_displayRefList.empty()) + { + /* Update reference list and append null terminator to array */ + /*g_displayRefList.clear(); + g_displayRefList.reserve(g_displayList.size() + 1); + for (const auto& display : g_displayList) + g_displayRefList.push_back(display.get()); + g_displayRefList.push_back(nullptr); + } + return g_displayRefList.data(); + */ + + return nullptr; +} + +Display* Display::Get(std::size_t index) +{ + //UpdateDisplayList(); + //return (index < g_displayList.size() ? g_displayList[index].get() : nullptr); + return nullptr; +} + +Display* Display::GetPrimary() +{ + //UpdateDisplayList(); + return nullptr; +} + +bool Display::ShowCursor(bool show) +{ + //TODO + return false; +} + +bool Display::IsCursorShown() +{ + //TODO + return true; +} + +bool Display::SetCursorPosition(const Offset2D& position) +{ + return true; +} + +Offset2D Display::GetCursorPosition() +{ + Offset2D rootPosition = { 0, 0 }; + Offset2D childPosition = { 0, 0 }; + return rootPosition; +} + + +/* + * EmscriptenDisplay class + */ + +EmscriptenDisplay::EmscriptenDisplay(int screenIndex) : + screen_ { screenIndex } +{ +} + +bool EmscriptenDisplay::IsPrimary() const +{ + return true; +} + +UTF8String EmscriptenDisplay::GetDeviceName() const +{ + return UTF8String{"device name"}; +} + +Offset2D EmscriptenDisplay::GetOffset() const +{ + /* Get display offset from position of root window */ + return Offset2D + { + 0, 0 + }; +} + +float EmscriptenDisplay::GetScale() const +{ + return 1.0f; // dummy +} + +bool EmscriptenDisplay::ResetDisplayMode() +{ + //TODO + return false; +} + +bool EmscriptenDisplay::SetDisplayMode(const DisplayMode& displayMode) +{ + /* Get all screen sizes from X11 extension Xrandr */ + int numSizes = 0; + + /* + XRRScreenSize* scrSizes = XRRSizes(GetNative(), screen_, &numSizes); + + for (int i = 0; i < numSizes; ++i) + { + // Check if specified display mode resolution matches this screen configuration + if (displayMode.resolution.width == static_cast(scrSizes[i].width) && + displayMode.resolution.height == static_cast(scrSizes[i].height)) + { + if (XRRScreenConfiguration* scrCfg = XRRGetScreenInfo(dpy, rootWnd)) + { + Status status = XRRSetScreenConfig(dpy, scrCfg, rootWnd, i, RR_Rotate_0, 0); + XRRFreeScreenConfigInfo(scrCfg); + return (status != 0); + } + } + } + */ + + return false; +} + +DisplayMode EmscriptenDisplay::GetDisplayMode() const +{ + DisplayMode displayMode; + + return displayMode; +} + +std::vector EmscriptenDisplay::GetSupportedDisplayModes() const +{ + std::vector displayModes; + + DisplayMode displayMode; + + return displayModes; +} + + +/* + * ======= Private: ======= + */ + + + +} // /namespace LLGL + + + +// ================================================================================ diff --git a/sources/Platform/Emscripten/EmscriptenDisplay.h b/sources/Platform/Emscripten/EmscriptenDisplay.h new file mode 100644 index 0000000000..20041c4368 --- /dev/null +++ b/sources/Platform/Emscripten/EmscriptenDisplay.h @@ -0,0 +1,60 @@ +/* + * LinuxDisplay.h + * + * Copyright (c) 2015 Lukas Hermanns. All rights reserved. + * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). + */ + +#ifndef LLGL_EMSCRIPTEN_DISPLAY_H +#define LLGL_EMSCRIPTEN_DISPLAY_H + + +#include +#include + +#include +#include +#include +#include + +namespace LLGL +{ + + +class EmscriptenDisplay : public Display +{ + + public: + + EmscriptenDisplay(int screenIndex); + + bool IsPrimary() const override; + + UTF8String GetDeviceName() const override; + + Offset2D GetOffset() const override; + float GetScale() const override; + + bool ResetDisplayMode() override; + bool SetDisplayMode(const DisplayMode& displayMode) override; + DisplayMode GetDisplayMode() const override; + + std::vector GetSupportedDisplayModes() const override; + + private: + + private: + + int screen_ = 0; + +}; + + +} // /namespace LLGL + + +#endif + + + +// ================================================================================ diff --git a/sources/Platform/Emscripten/EmscriptenModule.cpp b/sources/Platform/Emscripten/EmscriptenModule.cpp new file mode 100644 index 0000000000..4840112ea6 --- /dev/null +++ b/sources/Platform/Emscripten/EmscriptenModule.cpp @@ -0,0 +1,112 @@ +/* + * LinuxModule.cpp + * + * Copyright (c) 2015 Lukas Hermanns. All rights reserved. + * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). + */ + +#include "EmscriptenModule.h" +#include "../../Core/CoreUtils.h" +#include "../../Core/Exception.h" +#include +#include +#include +#include +#include + + +namespace LLGL +{ + + +// Returns absolute path of program instance +static std::string GetProgramPath() +{ + /* Get filename of running program */ + char buf[1024] = { 0 }; + ssize_t bufSize = readlink("/proc/self/exe", buf, sizeof(buf)); + if (bufSize == -1) + { + int errorCode = errno; + LLGL_TRAP("readlink(/proc/self/exe) failed: errno=%d (%s)", errorCode, strerror(errorCode)); + } + + /* Get path from program */ + std::string path = buf; + + std::size_t pathEnd = path.find_last_of('/'); + if (pathEnd != std::string::npos) + path.resize(pathEnd + 1); + + return path; +} + +std::string Module::GetModuleFilename(const char* moduleName) +{ + /* Extend module name to Linux shared library name (SO) */ + std::string s = GetProgramPath(); + s += "libLLGL_"; + s += moduleName; + #ifdef LLGL_DEBUG + s += "D"; + #endif + s += ".so"; + return s; +} + +bool Module::IsAvailable(const char* moduleFilename) +{ + /* Check if Linux shared library can be loaded properly */ + if (void* handle = dlopen(moduleFilename, RTLD_LAZY)) + { + dlclose(handle); + return true; + } + return false; +} + +std::unique_ptr Module::Load(const char* moduleFilename, Report* report) +{ + std::unique_ptr module = MakeUnique(moduleFilename, report); + return (module->IsValid() ? std::move(module) : nullptr); +} + +EmscriptenModule::EmscriptenModule(const char* moduleFilename, Report* report) +{ + /* Open Linux shared library */ + handle_ = dlopen(moduleFilename, RTLD_LAZY); + + /* Check if loading has failed */ + if (!handle_ && report != nullptr) + { + /* Append error message from most recent call to 'dlopen' */ + std::string appendix; + if (const char* err = dlerror()) + { + appendix += "; "; + appendix += err; + } + + /* Throw error message */ + report->Errorf("failed to load shared library (SO): \"%s\"%s\n", moduleFilename, appendix.c_str()); + } +} + +EmscriptenModule::~EmscriptenModule() +{ + if (handle_ != nullptr) + dlclose(handle_); +} + +void* EmscriptenModule::LoadProcedure(const char* procedureName) +{ + /* Get procedure address from library module and return it as raw-pointer */ + return dlsym(handle_, procedureName); +} + + +} // /namespace LLGL + + + +// ================================================================================ diff --git a/sources/Platform/Emscripten/EmscriptenModule.h b/sources/Platform/Emscripten/EmscriptenModule.h new file mode 100644 index 0000000000..f9a09ce3b9 --- /dev/null +++ b/sources/Platform/Emscripten/EmscriptenModule.h @@ -0,0 +1,50 @@ +/* + * LinuxModule.h + * + * Copyright (c) 2015 Lukas Hermanns. All rights reserved. + * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). + */ + +#ifndef LLGL_EMSCRIPTEN_MODULE_H +#define LLGL_EMSCRIPTEN_MODULE_H + + +#include "../Module.h" + + +namespace LLGL +{ + + +class EmscriptenModule : public Module +{ + + public: + + EmscriptenModule(const char* moduleFilename, Report* report = nullptr); + ~EmscriptenModule(); + + void* LoadProcedure(const char* procedureName) override; + + public: + + inline bool IsValid() const + { + return (handle_ != nullptr); + } + + private: + + void* handle_ = nullptr; + +}; + + +} // /namespace LLGL + + +#endif + + + +// ================================================================================ diff --git a/sources/Platform/Emscripten/EmscriptenPath.cpp b/sources/Platform/Emscripten/EmscriptenPath.cpp new file mode 100644 index 0000000000..d05f97dd5b --- /dev/null +++ b/sources/Platform/Emscripten/EmscriptenPath.cpp @@ -0,0 +1,42 @@ +/* + * LinuxPath.cpp + * + * Copyright (c) 2015 Lukas Hermanns. All rights reserved. + * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). + */ + +#include "../Path.h" +#include + + +namespace LLGL +{ + +namespace Path +{ + + +LLGL_EXPORT char GetSeparator() +{ + return '/'; +} + +LLGL_EXPORT UTF8String GetWorkingDir() +{ + char path[PATH_MAX] = { 0 }; + return UTF8String{ ::getcwd(path, sizeof(path)) }; +} + +LLGL_EXPORT UTF8String GetAbsolutePath(const StringView& filename) +{ + return Combine(GetWorkingDir(), filename); +} + + +} // /nameapace Path + +} // /namespace LLGL + + + +// ================================================================================ diff --git a/sources/Platform/Emscripten/EmscriptenTimer.cpp b/sources/Platform/Emscripten/EmscriptenTimer.cpp new file mode 100644 index 0000000000..321917a567 --- /dev/null +++ b/sources/Platform/Emscripten/EmscriptenTimer.cpp @@ -0,0 +1,45 @@ +/* + * LinuxTimer.cpp + * + * Copyright (c) 2015 Lukas Hermanns. All rights reserved. + * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). + */ + +#include +#include + + +namespace LLGL +{ + +namespace Timer +{ + + +static const std::uint64_t g_nsecFrequency = 1000000000ull; + +LLGL_EXPORT std::uint64_t Frequency() +{ + return g_nsecFrequency; +} + +static std::uint64_t MonotonicTimeToUInt64(const timespec& t) +{ + return (static_cast(t.tv_sec) * g_nsecFrequency + static_cast(t.tv_nsec)); +} + +LLGL_EXPORT std::uint64_t Tick() +{ + timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + return MonotonicTimeToUInt64(t); +} + + +} // /namespace Timer + +} // /namespace LLGL + + + +// ================================================================================ diff --git a/sources/Platform/Emscripten/EmscriptenWindow.cpp b/sources/Platform/Emscripten/EmscriptenWindow.cpp new file mode 100644 index 0000000000..2d347f6ec0 --- /dev/null +++ b/sources/Platform/Emscripten/EmscriptenWindow.cpp @@ -0,0 +1,501 @@ +/* + * EmscriptenWindow.cpp + * + * Copyright (c) 2015 Lukas Hermanns. All rights reserved. + * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). + */ + +#include +#include +#include "EmscriptenWindow.h" +#include "MapKey.h" +#include "../../Core/CoreUtils.h" +#include +#include + +namespace LLGL +{ + + +/* + * Surface class + */ + +bool Surface::ProcessEvents() +{ + + + return true; +} + + +/* + * Window class + */ + +static Offset2D GetScreenCenteredPosition(const Extent2D& size) +{ + if (auto display = Display::GetPrimary()) + { + const auto resolution = display->GetDisplayMode().resolution; + return + { + static_cast((resolution.width - size.width )/2), + static_cast((resolution.height - size.height)/2), + }; + } + return {}; +} + +std::unique_ptr Window::Create(const WindowDescriptor& desc) +{ + return MakeUnique(desc); +} + + +/* + * EmscriptenWindow class + */ + +EmscriptenWindow::EmscriptenWindow(const WindowDescriptor& desc) : + desc_ { desc } +{ + CreateEmscriptenWindow(); +} + +EmscriptenWindow::~EmscriptenWindow() +{ + +} + +bool EmscriptenWindow::GetNativeHandle(void* nativeHandle, std::size_t nativeHandleSize) +{ + if (nativeHandle != nullptr && nativeHandleSize == sizeof(NativeHandle)) + { + auto* handle = reinterpret_cast(nativeHandle); + //handle->visual = visual_; + return true; + } + return false; +} + +void EmscriptenWindow::ResetPixelFormat() +{ + // dummy +} + +Extent2D EmscriptenWindow::GetContentSize() const +{ + /* Return the size of the client area */ + return GetSize(true); +} + +void EmscriptenWindow::SetPosition(const Offset2D& position) +{ + /* Move window and store new position */ + //XMoveWindow(display_, wnd_, position.x, position.y); + //desc_.position = position; +} + +Offset2D EmscriptenWindow::GetPosition() const +{ + //XWindowAttributes attribs; + //XGetWindowAttributes(display_, wnd_, &attribs); + //return { attribs.x, attribs.y }; + + return {0, 0}; +} + +void EmscriptenWindow::SetSize(const Extent2D& size, bool useClientArea) +{ + //XResizeWindow(display_, wnd_, size.width, size.height); +} + +Extent2D EmscriptenWindow::GetSize(bool useClientArea) const +{ + /* + XWindowAttributes attribs; + XGetWindowAttributes(display_, wnd_, &attribs); + */ + + return Extent2D + { + static_cast(0), + static_cast(0) + }; +} + +void EmscriptenWindow::SetTitle(const UTF8String& title) +{ + //XStoreName(display_, wnd_, title.c_str()); +} + +UTF8String EmscriptenWindow::GetTitle() const +{ + char* title = nullptr; + //XFetchName(display_, wnd_, &title); + return title; +} + +void EmscriptenWindow::Show(bool show) +{ + if (show) + { + /* Map window and reset window position */ + //XMapWindow(display_, wnd_); + //XMoveWindow(display_, wnd_, desc_.position.x, desc_.position.y); + } + else + { + //XUnmapWindow(display_, wnd_); + } + + if ((desc_.flags & WindowFlags::Borderless) != 0) + { + //XSetInputFocus(display_, (show ? wnd_ : None), RevertToParent, CurrentTime); + } +} + +bool EmscriptenWindow::IsShown() const +{ + return false; +} + +void EmscriptenWindow::SetDesc(const WindowDescriptor& desc) +{ + //todo... +} + +WindowDescriptor EmscriptenWindow::GetDesc() const +{ + return desc_; //todo... +} + +void EmscriptenWindow::ProcessEvent(/*XEvent& event*/) +{ + /* + switch (event.type) + { + case KeyPress: + ProcessKeyEvent(event.xkey, true); + break; + + case KeyRelease: + ProcessKeyEvent(event.xkey, false); + break; + + case ButtonPress: + ProcessMouseKeyEvent(event.xbutton, true); + break; + + case ButtonRelease: + ProcessMouseKeyEvent(event.xbutton, false); + break; + + case Expose: + ProcessExposeEvent(); + break; + + case MotionNotify: + ProcessMotionEvent(event.xmotion); + break; + + case DestroyNotify: + PostQuit(); + break; + + case ClientMessage: + ProcessClientMessage(event.xclient); + break; + } + */ +} + + +/* + * ======= Private: ======= + */ + +/* +EM_JS(emscripten::EM_VAL, get_canvas, (), { + let canvas = document.getElementById("mycanvas"); + return Emval.toHandle(canvas); +}); +canvas_ = emscripten::val::take_ownership(get_canvas()); +*/ + +static inline const char *emscripten_event_type_to_string(int eventType) +{ + const char *events[] = { "(invalid)", "(none)", "keypress", "keydown", "keyup", "click", "mousedown", "mouseup", "dblclick", "mousemove", "wheel", "resize", + "scroll", "blur", "focus", "focusin", "focusout", "deviceorientation", "devicemotion", "orientationchange", "fullscreenchange", "pointerlockchange", + "visibilitychange", "touchstart", "touchend", "touchmove", "touchcancel", "gamepadconnected", "gamepaddisconnected", "beforeunload", + "batterychargingchange", "batterylevelchange", "webglcontextlost", "webglcontextrestored", "(invalid)" }; + + ++eventType; + + if (eventType < 0) + eventType = 0; + + if (eventType >= sizeof(events)/sizeof(events[0])) + eventType = sizeof(events)/sizeof(events[0])-1; + + return events[eventType]; +} + +const char *emscripten_result_to_string(EMSCRIPTEN_RESULT result) +{ + if (result == EMSCRIPTEN_RESULT_SUCCESS) return "EMSCRIPTEN_RESULT_SUCCESS"; + if (result == EMSCRIPTEN_RESULT_DEFERRED) return "EMSCRIPTEN_RESULT_DEFERRED"; + if (result == EMSCRIPTEN_RESULT_NOT_SUPPORTED) return "EMSCRIPTEN_RESULT_NOT_SUPPORTED"; + if (result == EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED) return "EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED"; + if (result == EMSCRIPTEN_RESULT_INVALID_TARGET) return "EMSCRIPTEN_RESULT_INVALID_TARGET"; + if (result == EMSCRIPTEN_RESULT_UNKNOWN_TARGET) return "EMSCRIPTEN_RESULT_UNKNOWN_TARGET"; + if (result == EMSCRIPTEN_RESULT_INVALID_PARAM) return "EMSCRIPTEN_RESULT_INVALID_PARAM"; + if (result == EMSCRIPTEN_RESULT_FAILED) return "EMSCRIPTEN_RESULT_FAILED"; + if (result == EMSCRIPTEN_RESULT_NO_DATA) return "EMSCRIPTEN_RESULT_NO_DATA"; + return "Unknown EMSCRIPTEN_RESULT!"; +} + +int interpret_charcode_for_keyevent(int eventType, const EmscriptenKeyboardEvent *e) +{ + // Only KeyPress events carry a charCode. For KeyDown and KeyUp events, these don't seem to be present yet, until later when the KeyDown + // is transformed to KeyPress. Sometimes it can be useful to already at KeyDown time to know what the charCode of the resulting + // KeyPress will be. The following attempts to do this: + if (eventType == EMSCRIPTEN_EVENT_KEYPRESS && e->which) return e->which; + if (e->charCode) return e->charCode; + if (strlen(e->key) == 1) return (int)e->key[0]; + if (e->which) return e->which; + return e->keyCode; +} + +const char* EmscriptenWindow::OnBeforeUnloadCallback(int eventType, const void* reserved, void* userData) +{ + EmscriptenWindow *emscriptenWindow = ((EmscriptenWindow*)userData); + //cleanup the window + return NULL; +} + +int EmscriptenWindow::OnCanvasResizeCallback(int eventType, const EmscriptenUiEvent *keyEvent, void *userData) +{ + EmscriptenWindow* window = (EmscriptenWindow*)userData; + + unsigned int width, height; + width = keyEvent->windowInnerWidth; + height = keyEvent->windowInnerHeight; + //EM_ASM({console.log("OnCanvasResizeCallback");}); + window->PostResize(Extent2D{ width, height }); + return 0; +} + +EM_BOOL EmscriptenWindow::OnKeyCallback(int eventType, const EmscriptenKeyboardEvent *e, void *userData) +{ + EmscriptenWindow* window = (EmscriptenWindow*)userData; + + int dom_pk_code = emscripten_compute_dom_pk_code(e->code); + + /* + printf("%s, key: \"%s\", code: \"%s\" = %s (%d), location: %lu,%s%s%s%s repeat: %d, locale: \"%s\", char: \"%s\", charCode: %lu (interpreted: %d), keyCode: %s(%lu), which: %lu\n", + emscripten_event_type_to_string(eventType), e->key, e->code, emscripten_dom_pk_code_to_string(dom_pk_code), dom_pk_code, e->location, + e->ctrlKey ? " CTRL" : "", e->shiftKey ? " SHIFT" : "", e->altKey ? " ALT" : "", e->metaKey ? " META" : "", + e->repeat, e->locale, e->charValue, e->charCode, interpret_charcode_for_keyevent(eventType, e), emscripten_dom_vk_to_string(e->keyCode), e->keyCode, e->which); + + if (eventType == EMSCRIPTEN_EVENT_KEYUP) printf("\n"); // Visual cue + */ + + printf("%s, key: \"%s\", code: \"%s\" = %s (%d)\n", emscripten_event_type_to_string(eventType), e->key, e->code, emscripten_dom_pk_code_to_string(dom_pk_code), dom_pk_code); + EmscriptenWindow *emscriptenWindow = ((EmscriptenWindow*)userData); + + auto key = MapKey(e->code); + + if (eventType == 2) + window->PostKeyDown(key); + else if (eventType == 3) + window->PostKeyUp(key); + + return true; +} + +EM_BOOL EmscriptenWindow::OnMouseCallback(int eventType, const EmscriptenMouseEvent *e, void *userData) +{ + /* + printf("%s, screen: (%ld,%ld), client: (%ld,%ld),%s%s%s%s button: %hu, buttons: %hu, movement: (%ld,%ld), canvas: (%ld,%ld), target: (%ld, %ld)\n", + emscripten_event_type_to_string(eventType), e->screenX, e->screenY, e->clientX, e->clientY, + e->ctrlKey ? " CTRL" : "", e->shiftKey ? " SHIFT" : "", e->altKey ? " ALT" : "", e->metaKey ? " META" : "", + e->button, e->buttons, e->movementX, e->movementY, e->canvasX, e->canvasY, e->targetX, e->targetY); + */ + + /* + if (e->screenX != 0 && e->screenY != 0 && e->clientX != 0 && e->clientY != 0 && e->canvasX != 0 && e->canvasY != 0 && e->targetX != 0 && e->targetY != 0) + { + if (eventType == EMSCRIPTEN_EVENT_CLICK) gotClick = 1; + if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN && e->buttons != 0) gotMouseDown = 1; + if (eventType == EMSCRIPTEN_EVENT_DBLCLICK) gotDblClick = 1; + if (eventType == EMSCRIPTEN_EVENT_MOUSEUP) gotMouseUp = 1; + if (eventType == EMSCRIPTEN_EVENT_MOUSEMOVE && (e->movementX != 0 || e->movementY != 0)) gotMouseMove = 1; + } + */ + + /* + if (eventType == EMSCRIPTEN_EVENT_CLICK && e->screenX == -500000) + { + printf("ERROR! Received an event to a callback that should have been unregistered!\n"); + gotClick = 0; + } + */ + + return EMSCRIPTEN_RESULT_SUCCESS; +} + +EM_BOOL EmscriptenWindow::OnWheelCallback(int eventType, const EmscriptenWheelEvent *e, void *userData) +{ + /* + printf("%s, screen: (%ld,%ld), client: (%ld,%ld),%s%s%s%s button: %hu, buttons: %hu, canvas: (%ld,%ld), target: (%ld, %ld), delta:(%g,%g,%g), deltaMode:%lu\n", + emscripten_event_type_to_string(eventType), e->mouse.screenX, e->mouse.screenY, e->mouse.clientX, e->mouse.clientY, + e->mouse.ctrlKey ? " CTRL" : "", e->mouse.shiftKey ? " SHIFT" : "", e->mouse.altKey ? " ALT" : "", e->mouse.metaKey ? " META" : "", + e->mouse.button, e->mouse.buttons, e->mouse.canvasX, e->mouse.canvasY, e->mouse.targetX, e->mouse.targetY, + (float)e->deltaX, (float)e->deltaY, (float)e->deltaZ, e->deltaMode); + */ + + /* + if (e->deltaY > 0.f || e->deltaY < 0.f) + gotWheel = 1; + */ + + return EMSCRIPTEN_RESULT_SUCCESS; +} + +void EmscriptenWindow::CreateEmscriptenWindow() +{ + /* Find canvas handle*/ + emscripten::val config = emscripten::val::module_property("config"); + emscripten::val document = emscripten::val::global("document"); + + if (config.isUndefined() || config.isNull()) + return; + + + if (!config.hasOwnProperty("canvas_selector")) + return; + + + std::string canvas_selector = config["canvas_selector"].as(); + canvas_ = document["body"].call("querySelector", canvas_selector); + + //EM_ASM({console.log("isUndefined");}); + + EM_ASM({ + console.log(Emval.toValue($0)); + }, canvas_.as_handle()); + + //emscripten::val console = emscripten::val::global("console"); + //console.call("log", canvas); + + /* Get final window position */ + if ((desc_.flags & WindowFlags::Centered) != 0) + desc_.position = GetScreenCenteredPosition(desc_.size); + + /* Set title and show window (if enabled) */ + SetTitle(desc_.title); + + /* Show window */ + if ((desc_.flags & WindowFlags::Visible) != 0) + { + + } + + /* Prepare borderless window */ + const bool isBorderless = ((desc_.flags & WindowFlags::Borderless) != 0); + if (isBorderless) + { + } + + /* Set callbacks */ + EMSCRIPTEN_RESULT ret; + ret = emscripten_set_beforeunload_callback((void*)this, EmscriptenWindow::OnBeforeUnloadCallback); + ret = emscripten_set_resize_callback(canvas_selector.c_str(), (void*)this, true, EmscriptenWindow::OnCanvasResizeCallback); + + ret = emscripten_set_keydown_callback(canvas_selector.c_str(), (void*)this, true, EmscriptenWindow::OnKeyCallback); + ret = emscripten_set_keyup_callback(canvas_selector.c_str(), (void*)this, true, EmscriptenWindow::OnKeyCallback); + + ret = emscripten_set_click_callback(canvas_selector.c_str(), (void*)this, true, EmscriptenWindow::OnMouseCallback); + ret = emscripten_set_mousedown_callback(canvas_selector.c_str(), (void*)this, true, EmscriptenWindow::OnMouseCallback); + ret = emscripten_set_mouseup_callback(canvas_selector.c_str(), (void*)this, true, EmscriptenWindow::OnMouseCallback); + ret = emscripten_set_dblclick_callback(canvas_selector.c_str(), (void*)this, true, EmscriptenWindow::OnMouseCallback); + ret = emscripten_set_mousemove_callback(canvas_selector.c_str(), (void*)this, true, EmscriptenWindow::OnMouseCallback); + ret = emscripten_set_wheel_callback(canvas_selector.c_str(), (void*)this, true, EmscriptenWindow::OnWheelCallback); +} + +void EmscriptenWindow::ProcessKeyEvent(/*XKeyEvent& event, bool down*/) +{ + /*auto key = MapKey(event); + if (down) + PostKeyDown(key); + else + PostKeyUp(key);*/ +} + +void EmscriptenWindow::ProcessMouseKeyEvent(/*XButtonEvent& event, bool down*/) +{ + /*switch (event.button) + { + case Button1: + PostMouseKeyEvent(Key::LButton, down); + break; + case Button2: + PostMouseKeyEvent(Key::MButton, down); + break; + case Button3: + PostMouseKeyEvent(Key::RButton, down); + break; + case Button4: + PostWheelMotion(1); + break; + case Button5: + PostWheelMotion(-1); + break; + }*/ +} + +void EmscriptenWindow::ProcessExposeEvent() +{ + //XWindowAttributes attribs; + //XGetWindowAttributes(display_, wnd_, &attribs); + + const Extent2D size + { + static_cast(0), + static_cast(0) + }; + + PostResize(size); +} + +void EmscriptenWindow::ProcessClientMessage(/*XClientMessageEvent& event*/) +{ + /*Atom atom = static_cast(event.data.l[0]); + if (atom == closeWndAtom_) + PostQuit();*/ +} + +void EmscriptenWindow::ProcessMotionEvent(/*XMotionEvent& event*/) +{ + /*const Offset2D mousePos { event.x, event.y }; + PostLocalMotion(mousePos); + PostGlobalMotion({ mousePos.x - prevMousePos_.x, mousePos.y - prevMousePos_.y }); + prevMousePos_ = mousePos;*/ +} + +void EmscriptenWindow::PostMouseKeyEvent(Key key, bool down) +{ + if (down) + PostKeyDown(key); + else + PostKeyUp(key); +} + + +} // /namespace LLGL + + + +// ================================================================================ diff --git a/sources/Platform/Emscripten/EmscriptenWindow.h b/sources/Platform/Emscripten/EmscriptenWindow.h new file mode 100644 index 0000000000..a6ddee521b --- /dev/null +++ b/sources/Platform/Emscripten/EmscriptenWindow.h @@ -0,0 +1,89 @@ +/* + * LinuxWindow.h + * + * Copyright (c) 2015 Lukas Hermanns. All rights reserved. + * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). + */ + +#ifndef LLGL_EMSCRIPTEN_WINDOW_H +#define LLGL_EMSCRIPTEN_WINDOW_H + + +#include +#include "EmscriptenDisplay.h" + +namespace LLGL +{ + + +class EmscriptenWindow : public Window +{ + + public: + + EmscriptenWindow(const WindowDescriptor& desc); + ~EmscriptenWindow(); + + bool GetNativeHandle(void* nativeHandle, std::size_t nativeHandleSize) override; + + void ResetPixelFormat() override; + + Extent2D GetContentSize() const override; + + void SetPosition(const Offset2D& position) override; + Offset2D GetPosition() const override; + + void SetSize(const Extent2D& size, bool useClientArea = true) override; + Extent2D GetSize(bool useClientArea = true) const override; + + void SetTitle(const UTF8String& title) override; + UTF8String GetTitle() const override; + + void Show(bool show = true) override; + bool IsShown() const override; + + void SetDesc(const WindowDescriptor& desc) override; + WindowDescriptor GetDesc() const override; + + public: + + void ProcessEvent(/*XEvent& event*/); + + private: + + void CreateEmscriptenWindow(); + + void ProcessKeyEvent(/*XKeyEvent& event, bool down*/); + void ProcessMouseKeyEvent(/*XButtonEvent& event, bool down*/); + void ProcessExposeEvent(); + void ProcessClientMessage(/*XClientMessageEvent& event*/); + void ProcessMotionEvent(/*XMotionEvent& event*/); + + void PostMouseKeyEvent(Key key, bool down); + + private: + static const char* OnBeforeUnloadCallback(int eventType, const void* reserved, void* userData); + static int OnCanvasResizeCallback(int eventType, const EmscriptenUiEvent *keyEvent, void *userData); + + static EM_BOOL OnKeyCallback(int eventType, const EmscriptenKeyboardEvent *e, void *userData); + + static EM_BOOL OnMouseCallback(int eventType, const EmscriptenMouseEvent *e, void *userData); + static EM_BOOL OnWheelCallback(int eventType, const EmscriptenWheelEvent *e, void *userData); + + private: + + WindowDescriptor desc_; + emscripten::val canvas_; + Offset2D prevMousePos_; + +}; + + +} // /namespace LLGL + + +#endif + + + +// ================================================================================ diff --git a/sources/Platform/Emscripten/MapKey.cpp b/sources/Platform/Emscripten/MapKey.cpp new file mode 100644 index 0000000000..0f95ac6cd2 --- /dev/null +++ b/sources/Platform/Emscripten/MapKey.cpp @@ -0,0 +1,202 @@ +/* + * MapKey.cpp (Linux) + * + * Copyright (c) 2015 Lukas Hermanns. All rights reserved. + * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). + */ + +#include "MapKey.h" +#include +#include + + +namespace LLGL +{ + + +#define KEYPAIR(KEYSYM, KEY) { KEYSYM, Key::KEY } + +static std::map GenerateEmscriptenKeyCodeMap() +{ + return + { + KEYPAIR(DOM_PK_ESCAPE , Escape), /* "Escape" */ + + KEYPAIR(DOM_PK_0 , D0), /* "Digit0" */ + KEYPAIR(DOM_PK_1 , D1), /* "Digit1" */ + KEYPAIR(DOM_PK_2 , D2), /* "Digit2" */ + KEYPAIR(DOM_PK_3 , D3), /* "Digit3" */ + KEYPAIR(DOM_PK_4 , D4), /* "Digit4" */ + KEYPAIR(DOM_PK_5 , D5), /* "Digit5" */ + KEYPAIR(DOM_PK_6 , D6), /* "Digit6" */ + KEYPAIR(DOM_PK_7 , D7), /* "Digit7" */ + KEYPAIR(DOM_PK_8 , D8), /* "Digit8" */ + KEYPAIR(DOM_PK_9 , D9), /* "Digit9" */ + + KEYPAIR(DOM_PK_MINUS , Minus), /* "Minus" */ + KEYPAIR(DOM_PK_EQUAL , Any), /* "Equal" */ + KEYPAIR(DOM_PK_BACKSPACE , Back), /* "Backspace" */ + KEYPAIR(DOM_PK_TAB , Tab), /* "Tab" */ + + KEYPAIR(DOM_PK_Q , Q), /* "KeyQ" */ + KEYPAIR(DOM_PK_W , W), /* "KeyW" */ + KEYPAIR(DOM_PK_E , E), /* "KeyE" */ + KEYPAIR(DOM_PK_R , R), /* "KeyR" */ + KEYPAIR(DOM_PK_T , T), /* "KeyT" */ + KEYPAIR(DOM_PK_Y , Y), /* "KeyY" */ + KEYPAIR(DOM_PK_U , U), /* "KeyU" */ + KEYPAIR(DOM_PK_I , I), /* "KeyI" */ + KEYPAIR(DOM_PK_O , O), /* "KeyO" */ + KEYPAIR(DOM_PK_P , P), /* "KeyP" */ + //KEYPAIR(DOM_PK_BRACKET_LEFT , Any), /* "BracketLeft" */ + //KEYPAIR(DOM_PK_BRACKET_RIGHT , Any), /* "BracketRight" */ + KEYPAIR(DOM_PK_ENTER , Return), /* "Enter" */ + KEYPAIR(DOM_PK_CONTROL_LEFT , LControl), /* "ControlLeft" */ + KEYPAIR(DOM_PK_A , A), /* "KeyA" */ + KEYPAIR(DOM_PK_S , S), /* "KeyS" */ + KEYPAIR(DOM_PK_D , D), /* "KeyD" */ + KEYPAIR(DOM_PK_F , F), /* "KeyF" */ + KEYPAIR(DOM_PK_G , G), /* "KeyG" */ + KEYPAIR(DOM_PK_H , H), /* "KeyH" */ + KEYPAIR(DOM_PK_J , J), /* "KeyJ" */ + KEYPAIR(DOM_PK_K , K), /* "KeyK" */ + KEYPAIR(DOM_PK_L , L), /* "KeyL" */ + //KEYPAIR(DOM_PK_SEMICOLON , Any), /* "Semicolon" */ + //KEYPAIR(DOM_PK_QUOTE , Any), /* "Quote" */ + //KEYPAIR(DOM_PK_BACKQUOTE , Any), /* "Backquote" */ + KEYPAIR(DOM_PK_SHIFT_LEFT , LShift), /* "ShiftLeft" */ + //KEYPAIR(DOM_PK_BACKSLASH , Any), /* "Backslash" */ + KEYPAIR(DOM_PK_Z , Z), /* "KeyZ" */ + KEYPAIR(DOM_PK_X , X), /* "KeyX" */ + KEYPAIR(DOM_PK_C , C), /* "KeyC" */ + KEYPAIR(DOM_PK_V , V), /* "KeyV" */ + KEYPAIR(DOM_PK_B , B), /* "KeyB" */ + KEYPAIR(DOM_PK_N , N), /* "KeyN" */ + KEYPAIR(DOM_PK_M , M), /* "KeyM" */ + KEYPAIR(DOM_PK_COMMA , Comma), /* "Comma" */ + KEYPAIR(DOM_PK_PERIOD , Period), /* "Period" */ + //KEYPAIR(DOM_PK_SLASH , Any), /* "Slash" */ + KEYPAIR(DOM_PK_SHIFT_RIGHT , RShift), /* "ShiftRight" */ + KEYPAIR(DOM_PK_NUMPAD_MULTIPLY , KeypadMultiply), /* "NumpadMultiply" */ + KEYPAIR(DOM_PK_ALT_LEFT , LMenu), /* "AltLeft" */ + KEYPAIR(DOM_PK_SPACE , Space), /* "Space" */ + KEYPAIR(DOM_PK_CAPS_LOCK , Capital), /* "CapsLock" */ + KEYPAIR(DOM_PK_F1 , F1), /* "F1" */ + KEYPAIR(DOM_PK_F2 , F2), /* "F2" */ + KEYPAIR(DOM_PK_F3 , F3), /* "F3" */ + KEYPAIR(DOM_PK_F4 , F4), /* "F4" */ + KEYPAIR(DOM_PK_F5 , F5), /* "F5" */ + KEYPAIR(DOM_PK_F6 , F6), /* "F6" */ + KEYPAIR(DOM_PK_F7 , F7), /* "F7" */ + KEYPAIR(DOM_PK_F8 , F8), /* "F8" */ + KEYPAIR(DOM_PK_F9 , F9), /* "F9" */ + KEYPAIR(DOM_PK_F10 , F10), /* "F10" */ + + KEYPAIR(DOM_PK_PAUSE , Pause), /* "Pause" */ + KEYPAIR(DOM_PK_SCROLL_LOCK , ScrollLock), /* "ScrollLock" */ + KEYPAIR(DOM_PK_NUMPAD_7 , Keypad7), /* "Numpad7" */ + KEYPAIR(DOM_PK_NUMPAD_8 , Keypad8), /* "Numpad8" */ + KEYPAIR(DOM_PK_NUMPAD_9 , Keypad9), /* "Numpad9" */ + KEYPAIR(DOM_PK_NUMPAD_SUBTRACT , KeypadMinus), /* "NumpadSubtract" */ + KEYPAIR(DOM_PK_NUMPAD_4 , Keypad4), /* "Numpad4" */ + KEYPAIR(DOM_PK_NUMPAD_5 , Keypad5), /* "Numpad5" */ + KEYPAIR(DOM_PK_NUMPAD_6 , Keypad6), /* "Numpad6" */ + KEYPAIR(DOM_PK_NUMPAD_ADD , KeypadPlus), /* "NumpadAdd" */ + KEYPAIR(DOM_PK_NUMPAD_1 , Keypad1), /* "Numpad1" */ + KEYPAIR(DOM_PK_NUMPAD_2 , Keypad2), /* "Numpad2" */ + KEYPAIR(DOM_PK_NUMPAD_3 , Keypad3), /* "Numpad3" */ + KEYPAIR(DOM_PK_NUMPAD_0 , Keypad0), /* "Numpad0" */ + KEYPAIR(DOM_PK_NUMPAD_DECIMAL , KeypadDecimal), /* "NumpadDecimal" */ + KEYPAIR(DOM_PK_PRINT_SCREEN , Print), /* "PrintScreen" */ + //KEYPAIR(DOM_PK_INTL_BACKSLASH , Any), /* "IntlBackslash" */ + KEYPAIR(DOM_PK_F11 , F11), /* "F11" */ + KEYPAIR(DOM_PK_F12 , F12), /* "F12" */ + //KEYPAIR(DOM_PK_NUMPAD_EQUAL , Any), /* "NumpadEqual" */ + KEYPAIR(DOM_PK_F13 , F13), /* "F13" */ + KEYPAIR(DOM_PK_F14 , F14), /* "F14" */ + KEYPAIR(DOM_PK_F15 , F15), /* "F15" */ + KEYPAIR(DOM_PK_F16 , F16), /* "F16" */ + KEYPAIR(DOM_PK_F17 , F17), /* "F17" */ + KEYPAIR(DOM_PK_F18 , F18), /* "F18" */ + KEYPAIR(DOM_PK_F19 , F19), /* "F19" */ + KEYPAIR(DOM_PK_F20 , F20), /* "F20" */ + KEYPAIR(DOM_PK_F21 , F21), /* "F21" */ + KEYPAIR(DOM_PK_F22 , F22), /* "F22" */ + KEYPAIR(DOM_PK_F23 , F23), /* "F23" */ + //KEYPAIR(DOM_PK_KANA_MODE , Any), /* "KanaMode" */ + //KEYPAIR(DOM_PK_LANG_2 , Any), /* "Lang2" */ + //KEYPAIR(DOM_PK_LANG_1 , Any), /* "Lang1" */ + //KEYPAIR(DOM_PK_INTL_RO , Any), /* "IntlRo" */ + KEYPAIR(DOM_PK_F24 , F24), /* "F24" */ + //KEYPAIR(DOM_PK_CONVERT , Any), /* "Convert" */ + //KEYPAIR(DOM_PK_NON_CONVERT , Any), /* "NonConvert" */ + //KEYPAIR(DOM_PK_INTL_YEN , Any), /* "IntlYen" */ + KEYPAIR(DOM_PK_NUMPAD_COMMA , KeypadDecimal), /* "NumpadComma" */ + //KEYPAIR(DOM_PK_PASTE , Any), /* "Paste" */ + KEYPAIR(DOM_PK_MEDIA_TRACK_PREVIOUS , MediaPrevTrack), /* "MediaTrackPrevious" */ + //KEYPAIR(DOM_PK_CUT , Any), /* "Cut" */ + //KEYPAIR(DOM_PK_COPY , Any), /* "Copy" */ + KEYPAIR(DOM_PK_MEDIA_TRACK_NEXT , MediaNextTrack), /* "MediaTrackNext" */ + + KEYPAIR(DOM_PK_NUMPAD_ENTER , Return), /* "NumpadEnter" */ + KEYPAIR(DOM_PK_CONTROL_RIGHT , RControl), /* "ControlRight" */ + KEYPAIR(DOM_PK_AUDIO_VOLUME_MUTE , VolumeMute), /* "VolumeMute" */ + KEYPAIR(DOM_PK_LAUNCH_APP_2 , LaunchApp2), /* "LaunchApp2" */ + KEYPAIR(DOM_PK_MEDIA_PLAY_PAUSE , MediaPlayPause), /* "MediaPlayPause" */ + KEYPAIR(DOM_PK_MEDIA_STOP , MediaStop), /* "MediaStop" */ + //KEYPAIR(DOM_PK_EJECT , Any), /* "Eject" */ + KEYPAIR(DOM_PK_AUDIO_VOLUME_DOWN , VolumeDown), /* "VolumeDown" */ + KEYPAIR(DOM_PK_AUDIO_VOLUME_UP , VolumeUp), /* "VolumeUp" */ + KEYPAIR(DOM_PK_BROWSER_HOME , BrowserHome), /* "BrowserHome" */ + KEYPAIR(DOM_PK_NUMPAD_DIVIDE , KeypadDivide), /* "NumpadDivide" */ + KEYPAIR(DOM_PK_ALT_RIGHT , RMenu), /* "AltRight" */ + KEYPAIR(DOM_PK_HELP , Help), /* "Help" */ + KEYPAIR(DOM_PK_NUM_LOCK , NumLock), /* "NumLock" */ + KEYPAIR(DOM_PK_HOME , Home), /* "Home" */ + KEYPAIR(DOM_PK_ARROW_UP , Up), /* "ArrowUp" */ + KEYPAIR(DOM_PK_PAGE_UP , PageUp), /* "PageUp" */ + KEYPAIR(DOM_PK_ARROW_LEFT , Left), /* "ArrowLeft" */ + KEYPAIR(DOM_PK_ARROW_RIGHT , Right), /* "ArrowRight" */ + KEYPAIR(DOM_PK_END , End), /* "End" */ + KEYPAIR(DOM_PK_ARROW_DOWN , Down), /* "ArrowDown" */ + KEYPAIR(DOM_PK_PAGE_DOWN , PageDown), /* "PageDown" */ + KEYPAIR(DOM_PK_INSERT , Insert), /* "Insert" */ + KEYPAIR(DOM_PK_DELETE , Delete), /* "Delete" */ + KEYPAIR(DOM_PK_META_LEFT , LWin), /* "MetaLeft" */ + //KEYPAIR(DOM_PK_OS_LEFT , Any), /* "OSLeft" */ + KEYPAIR(DOM_PK_META_RIGHT , RWin), /* "MetaRight" */ + //KEYPAIR(DOM_PK_OS_RIGHT , Any), /* "OSRight" */ + KEYPAIR(DOM_PK_CONTEXT_MENU , Apps), /* "ContextMenu" */ + //KEYPAIR(DOM_PK_POWER , Any), /* "Power" */ + KEYPAIR(DOM_PK_BROWSER_SEARCH , BrowserSearch), /* "BrowserSearch" */ + KEYPAIR(DOM_PK_BROWSER_FAVORITES , BrowserFavorits), /* "BrowserFavorites" */ + KEYPAIR(DOM_PK_BROWSER_REFRESH , BrowserRefresh), /* "BrowserRefresh" */ + KEYPAIR(DOM_PK_BROWSER_STOP , BrowserStop), /* "BrowserStop" */ + KEYPAIR(DOM_PK_BROWSER_FORWARD , BrowserForward), /* "BrowserForward" */ + KEYPAIR(DOM_PK_BROWSER_BACK , BrowserBack), /* "BrowserBack" */ + KEYPAIR(DOM_PK_LAUNCH_APP_1 , LaunchApp1), /* "LaunchApp1" */ + KEYPAIR(DOM_PK_LAUNCH_MAIL , LaunchMail), /* "LaunchMail" */ + //KEYPAIR(DOM_PK_LAUNCH_MEDIA_PLAYER , Any), /* "LaunchMediaPlayer" */ + KEYPAIR(DOM_PK_MEDIA_SELECT , LaunchMediaSelect), /* "MediaSelect" */ + }; +}; + +static std::map g_emscriptenWindowKeyCodeMap = GenerateEmscriptenKeyCodeMap(); + +#undef KEYPAIR + + +Key MapKey(const char* keyEvent) +{ + int keyCode = emscripten_compute_dom_pk_code(keyEvent); + auto it = g_emscriptenWindowKeyCodeMap.find(keyCode); + return (it != g_emscriptenWindowKeyCodeMap.end() ? it->second : Key::Pause); +} + + + +} // /namespace LLGL + + + +// ================================================================================ diff --git a/sources/Platform/Emscripten/MapKey.h b/sources/Platform/Emscripten/MapKey.h new file mode 100644 index 0000000000..deb95033f0 --- /dev/null +++ b/sources/Platform/Emscripten/MapKey.h @@ -0,0 +1,30 @@ +/* + * MapKey.h + * + * Copyright (c) 2015 Lukas Hermanns. All rights reserved. + * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). + */ + +#ifndef LLGL_MAP_KEY_H +#define LLGL_MAP_KEY_H + + +#include +#include + + +namespace LLGL +{ + + +Key MapKey(const char* keyEvent); + + +} // /namespace LLGL + + +#endif + + + +// ================================================================================ diff --git a/sources/Renderer/OpenGL/CMakeLists.txt b/sources/Renderer/OpenGL/CMakeLists.txt index 61f5f23b44..043d90cd57 100644 --- a/sources/Renderer/OpenGL/CMakeLists.txt +++ b/sources/Renderer/OpenGL/CMakeLists.txt @@ -98,6 +98,9 @@ elseif(UNIX) if(LLGL_ANDROID_PLATFORM) find_source_files(FilesRendererGLPlatform CXX ${PROJECT_SOURCE_DIR}/Platform/Android) find_source_files(FilesIncludeGLPlatform INC ${BACKEND_INCLUDE_DIR}/OpenGL/Android) + elseif(EMSCRIPTEN) + find_source_files(FilesRendererGLPlatform CXX ${PROJECT_SOURCE_DIR}/Platform/Emscripten) + find_source_files(FilesIncludeGLPlatform INC ${BACKEND_INCLUDE_DIR}/OpenGL/Emscripten) else() find_source_files(FilesRendererGLPlatform CXX ${PROJECT_SOURCE_DIR}/Platform/Linux) find_source_files(FilesIncludeGLPlatform INC ${BACKEND_INCLUDE_DIR}/OpenGL/Linux) @@ -167,12 +170,14 @@ if(LLGL_BUILD_RENDERER_OPENGLES3) include("${EXTERNAL_MODULE_DIR}/FindOpenGLES3.cmake") if(OPENGLES_FOUND) include_directories(${OPENGLES_INCLUDE_DIR}) - + add_llgl_module(LLGL_OpenGLES3 LLGL_BUILD_RENDERER_OPENGLES3 "${FilesGLES3}") if(APPLE) target_link_libraries(LLGL_OpenGLES3 LLGL ${OPENGLES_LIBRARIES} "-framework Foundation -framework UIKit -framework QuartzCore -framework OpenGLES -framework GLKit") ADD_PROJECT_DEFINE(LLGL_OpenGLES3 GLES_SILENCE_DEPRECATION) + elseif(EMSCRIPTEN) + target_link_libraries(LLGL_OpenGLES3 LLGL ${OPENGLES_LIBRARIES}) else() target_link_libraries(LLGL_OpenGLES3 LLGL EGL GLESv3) endif() @@ -187,13 +192,13 @@ if(LLGL_BUILD_RENDERER_OPENGL) # OpenGL Renderer set(OpenGL_GL_PREFERENCE GLVND) find_package(OpenGL REQUIRED) - if(OpenGL_FOUND) + if(OPENGL_FOUND) include_directories(${OPENGL_INCLUDE_DIR}) add_llgl_module(LLGL_OpenGL LLGL_BUILD_RENDERER_OPENGL "${FilesGL}") - target_link_libraries(LLGL_OpenGL LLGL ${OPENGL_LIBRARIES}) - + target_link_libraries(LLGL_OpenGL LLGL ${OPENGL_LIBRARIES}) + if(APPLE) ADD_PROJECT_DEFINE(LLGL_OpenGL GL_SILENCE_DEPRECATION) endif() diff --git a/sources/Renderer/OpenGL/GLCoreProfile/OpenGLCore.h b/sources/Renderer/OpenGL/GLCoreProfile/OpenGLCore.h index 382f2f03a1..ac66d6fced 100644 --- a/sources/Renderer/OpenGL/GLCoreProfile/OpenGLCore.h +++ b/sources/Renderer/OpenGL/GLCoreProfile/OpenGLCore.h @@ -17,6 +17,8 @@ # include # include # include +#elif defined LLGL_OS_EMSCRIPTEN +# include #elif defined LLGL_OS_LINUX # include # include diff --git a/sources/Renderer/OpenGL/GLESProfile/OpenGLES.h b/sources/Renderer/OpenGL/GLESProfile/OpenGLES.h index 37bd788279..fcce88d29a 100644 --- a/sources/Renderer/OpenGL/GLESProfile/OpenGLES.h +++ b/sources/Renderer/OpenGL/GLESProfile/OpenGLES.h @@ -14,6 +14,11 @@ #if defined(LLGL_OS_IOS) # include # include +#elif defined(LLGL_OS_EMSCRIPTEN) +# define GL_GLEXT_PROTOTYPES +# define EGL_EGLEXT_PROTOTYPES +# include +# include #elif defined(LLGL_OS_ANDROID) // Include all GLES 3.0 functions with static linkage # include diff --git a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.cpp b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.cpp new file mode 100644 index 0000000000..056b04344b --- /dev/null +++ b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.cpp @@ -0,0 +1,170 @@ +/* + * EmscriptenGLContext.cpp + * + * Copyright (c) 2015 Lukas Hermanns. All rights reserved. + * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). + */ + +#include "EmscriptenGLContext.h" +#include "../../../CheckedCast.h" +#include "../../../StaticAssertions.h" +#include "../../../../Core/CoreUtils.h" +#include +#include + + +namespace LLGL +{ + + +/* + * GLContext class + */ + +LLGL_ASSERT_STDLAYOUT_STRUCT( OpenGL::RenderSystemNativeHandle ); + +std::unique_ptr GLContext::Create( + const GLPixelFormat& pixelFormat, + const RendererConfigurationOpenGL& profile, + Surface& surface, + GLContext* sharedContext, + const ArrayView& /*customNativeHandle*/) +{ + EmscriptenGLContext* sharedContextEGL = (sharedContext != nullptr ? LLGL_CAST(EmscriptenGLContext*, sharedContext) : nullptr); + return MakeUnique(pixelFormat, profile, surface, sharedContextEGL); +} + + +/* + * EmscriptenGLContext class + */ + +EmscriptenGLContext::EmscriptenGLContext( + const GLPixelFormat& pixelFormat, + const RendererConfigurationOpenGL& profile, + Surface& surface, + EmscriptenGLContext* sharedContext) +: + display_ { eglGetDisplay(EGL_DEFAULT_DISPLAY) } +{ + CreateContext(pixelFormat, profile, sharedContext); +} + +EmscriptenGLContext::~EmscriptenGLContext() +{ + DeleteContext(); +} + +int EmscriptenGLContext::GetSamples() const +{ + return samples_; +} + +bool EmscriptenGLContext::GetNativeHandle(void* nativeHandle, std::size_t nativeHandleSize) const +{ + if (nativeHandle != nullptr && nativeHandleSize == sizeof(OpenGL::RenderSystemNativeHandle)) + { + auto* nativeHandleGL = reinterpret_cast(nativeHandle); + nativeHandleGL->context = context_; + return true; + } + return false; +} + + +/* + * ======= Private: ======= + */ + +bool EmscriptenGLContext::SetSwapInterval(int interval) +{ + return (eglSwapInterval(display_, interval) == EGL_TRUE); +} + +bool EmscriptenGLContext::SelectConfig(const GLPixelFormat& pixelFormat) +{ + /* Look for a framebuffer configuration; reduce samples if necessary */ + for (samples_ = pixelFormat.samples; samples_ > 1; --samples_) + { + /* Initialize framebuffer configuration */ + const EGLint attribs[] = + { + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + //EGL_ALPHA_SIZE, 8, + EGL_DEPTH_SIZE, pixelFormat.depthBits, + EGL_STENCIL_SIZE, pixelFormat.stencilBits, + EGL_SAMPLE_BUFFERS, (samples_ > 1 ? 1 : 0), + EGL_SAMPLES, (samples_ > 1 ? samples_ : 1), + EGL_NONE + }; + + /* Choose configuration */ + EGLint numConfigs = 0; + EGLBoolean success = eglChooseConfig(display_, attribs, &config_, 1, &numConfigs); + + /* Reduce number of sample if configuration failed */ + if (success == EGL_TRUE && numConfigs >= 1) + { + SetDefaultColorFormat(); + DeduceDepthStencilFormat(pixelFormat.depthBits, pixelFormat.stencilBits); + return true; + } + } + + /* No suitable configuration found */ + return false; +} + +void EmscriptenGLContext::CreateContext( + const GLPixelFormat& pixelFormat, + const RendererConfigurationOpenGL& profile, + EmscriptenGLContext* sharedContext) +{ + /* Initialize EGL display connection (ignore major/minor output parameters) */ + if (!eglInitialize(display_, nullptr, nullptr)) + throw std::runtime_error("eglInitialize failed"); + + /* Select EGL context configuration for pixel format */ + if (!SelectConfig(pixelFormat)) + throw std::runtime_error("eglChooseConfig failed"); + + /* Set up EGL profile attributes */ + EGLint major = 3, minor = 0; + + if (!(profile.majorVersion == 0 && profile.minorVersion == 0)) + { + major = profile.majorVersion; + minor = profile.minorVersion; + } + + const EGLint contextAttribs[] = + { + EGL_CONTEXT_MAJOR_VERSION, major, + EGL_CONTEXT_MINOR_VERSION, minor, + #ifdef LLGL_DEBUG + EGL_CONTEXT_OPENGL_DEBUG, EGL_TRUE, + EGL_CONTEXT_OPENGL_ROBUST_ACCESS, EGL_TRUE, + #endif + EGL_NONE + }; + + /* Create EGL context with optional shared EGL context */ + EGLContext sharedEGLContext = (sharedContext != nullptr ? sharedContext->context_ : EGL_NO_CONTEXT); + context_ = eglCreateContext(display_, config_, sharedEGLContext, contextAttribs); + if (!context_) + throw std::runtime_error("eglCreateContext failed"); +} + +void EmscriptenGLContext::DeleteContext() +{ + eglDestroyContext(display_, context_); +} + + +} // /namespace LLGL + + + +// ================================================================================ diff --git a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.h b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.h new file mode 100644 index 0000000000..00ba22cfe4 --- /dev/null +++ b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.h @@ -0,0 +1,89 @@ +/* + * EmscriptenGLContext.h + * + * Copyright (c) 2015 Lukas Hermanns. All rights reserved. + * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). + */ + +#ifndef LLGL_EMSCRIPTEN_CONTEXT_H +#define LLGL_EMSCRIPTEN_CONTEXT_H + + +#include "../GLContext.h" +#include "../../OpenGL.h" +#include + + +namespace LLGL +{ + + +// Implementation of the interface for Android and wrapper for a native EGL context. +class EmscriptenGLContext : public GLContext +{ + + public: + + EmscriptenGLContext( + const GLPixelFormat& pixelFormat, + const RendererConfigurationOpenGL& profile, + Surface& surface, + EmscriptenGLContext* sharedContext + ); + ~EmscriptenGLContext(); + + int GetSamples() const override; + + bool GetNativeHandle(void* nativeHandle, std::size_t nativeHandleSize) const override; + + public: + + // Returns the native EGL display. + inline ::EGLDisplay GetEGLDisplay() const + { + return display_; + } + + // Returns the native EGL context. + inline ::EGLContext GetEGLContext() const + { + return context_; + } + + // Returns the native EGL configuration. + inline ::EGLConfig GetEGLConfig() const + { + return config_; + } + + private: + + bool SetSwapInterval(int interval) override; + + bool SelectConfig(const GLPixelFormat& pixelFormat); + + void CreateContext( + const GLPixelFormat& pixelFormat, + const RendererConfigurationOpenGL& profile, + EmscriptenGLContext* sharedContext + ); + void DeleteContext(); + + private: + + ::EGLDisplay display_ = nullptr; + ::EGLContext context_ = nullptr; + ::EGLConfig config_ = nullptr; + int samples_ = 1; + +}; + + +} // /namespace LLGL + + +#endif + + + +// ================================================================================ diff --git a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.cpp b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.cpp new file mode 100644 index 0000000000..09d0d54815 --- /dev/null +++ b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.cpp @@ -0,0 +1,81 @@ +/* + * EmscriptenGLSwapChainContext.cpp + * + * Copyright (c) 2015 Lukas Hermanns. All rights reserved. + * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). + */ + +#include "EmscriptenGLSwapChainContext.h" +#include "EmscriptenGLContext.h" +#include "../../../../Core/CoreUtils.h" +#include + + +namespace LLGL +{ + + +/* + * GLSwapChainContext class + */ + +std::unique_ptr GLSwapChainContext::Create(GLContext& context, Surface& surface) +{ + return MakeUnique(static_cast(context), surface); +} + +bool GLSwapChainContext::MakeCurrentUnchecked(GLSwapChainContext* context) +{ + return EmscriptenGLSwapChainContext::MakeCurrentEGLContext(static_cast(context)); +} + + +/* + * EmscriptenGLSwapChainContext class + */ + +EmscriptenGLSwapChainContext::EmscriptenGLSwapChainContext(EmscriptenGLContext& context, Surface& surface) : + GLSwapChainContext { context }, + display_ { context.GetEGLDisplay() }, + context_ { context.GetEGLContext() } +{ + /* Get native surface handle */ + NativeHandle nativeHandle; + surface.GetNativeHandle(&nativeHandle, sizeof(nativeHandle)); + + /* Create drawable surface */ + surface_ = eglCreateWindowSurface(display_, context.GetEGLConfig(), nativeHandle.window, nullptr); + if (!surface_) + throw std::runtime_error("eglCreateWindowSurface failed"); +} + +EmscriptenGLSwapChainContext::~EmscriptenGLSwapChainContext() +{ + eglDestroySurface(display_, surface_); +} + +bool EmscriptenGLSwapChainContext::SwapBuffers() +{ + eglSwapBuffers(display_, surface_); + return true; +} + +void EmscriptenGLSwapChainContext::Resize(const Extent2D& resolution) +{ + // dummy +} + +bool EmscriptenGLSwapChainContext::MakeCurrentEGLContext(EmscriptenGLSwapChainContext* context) +{ + if (context) + return eglMakeCurrent(context->display_, context->surface_, context->surface_, context->context_); + else + return eglMakeCurrent(eglGetDisplay(EGL_DEFAULT_DISPLAY), EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); +} + + +} // /namespace LLGL + + + +// ================================================================================ diff --git a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.h b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.h new file mode 100644 index 0000000000..7a1fb79600 --- /dev/null +++ b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.h @@ -0,0 +1,55 @@ +/* + * AndroidGLSwapChainContext.h + * + * Copyright (c) 2015 Lukas Hermanns. All rights reserved. + * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). + */ + +#ifndef LLGL_EMSCRIPTEN_GL_SWAP_CHAIN_CONTEXT_H +#define LLGL_EMSCRIPTEN_GL_SWAP_CHAIN_CONTEXT_H + + +#include "../GLSwapChainContext.h" +#include "../../OpenGL.h" +#include + + +namespace LLGL +{ + + +class Surface; +class EmscriptenGLContext; + +class EmscriptenGLSwapChainContext final : public GLSwapChainContext +{ + + public: + + EmscriptenGLSwapChainContext(EmscriptenGLContext& context, Surface& surface); + ~EmscriptenGLSwapChainContext(); + + bool SwapBuffers() override; + void Resize(const Extent2D& resolution) override; + + public: + + static bool MakeCurrentEGLContext(EmscriptenGLSwapChainContext* context); + + private: + + ::EGLDisplay display_ = nullptr; + ::EGLContext context_ = nullptr; + ::EGLSurface surface_ = nullptr; + +}; + + +} // /namespace LLGL + + +#endif + + + +// ================================================================================ From 45059ea5d333ec8a23cdc09ad34dc349ef0b6dc4 Mon Sep 17 00:00:00 2001 From: Victor Stanchevici Date: Mon, 18 Mar 2024 16:50:42 +0200 Subject: [PATCH 02/15] Update --- CMakePresets.json | 35 +++++++++++++++++++ .../Emscripten/EmscriptenGLContext.cpp | 2 +- .../EmscriptenGLSwapChainContext.cpp | 3 +- .../Emscripten/EmscriptenGLSwapChainContext.h | 2 +- 4 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 CMakePresets.json diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000000..b0e5371e38 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,35 @@ +{ + "version": 3, + "configurePresets": [ + { + "name": "emscripten", + "generator": "Ninja Multi-Config", + "toolchainFile": "/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake", + "binaryDir": "${sourceDir}/../llgl-build", + "architecture": { + "strategy": "external", + "value": "x86" + }, + "vendor": { + "microsoft.com/VisualStudioSettings/CMake/1.0": { "hostOS": [ "Linux" ] } + } + } + ], + "buildPresets": [ + { + "name": "Release", + "configurePreset": "emscripten", + "configuration": "Release" + }, + { + "name": "Debug", + "configurePreset": "emscripten", + "configuration": "Debug" + }, + { + "name": "RelWithDebInfo", + "configurePreset": "emscripten", + "configuration": "RelWithDebInfo" + } + ] +} diff --git a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.cpp b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.cpp index 056b04344b..7236f7000b 100644 --- a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.cpp +++ b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.cpp @@ -65,7 +65,7 @@ bool EmscriptenGLContext::GetNativeHandle(void* nativeHandle, std::size_t native if (nativeHandle != nullptr && nativeHandleSize == sizeof(OpenGL::RenderSystemNativeHandle)) { auto* nativeHandleGL = reinterpret_cast(nativeHandle); - nativeHandleGL->context = context_; + //nativeHandleGL->context = context_; return true; } return false; diff --git a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.cpp b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.cpp index 09d0d54815..0ff7f60a41 100644 --- a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.cpp +++ b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.cpp @@ -44,7 +44,8 @@ EmscriptenGLSwapChainContext::EmscriptenGLSwapChainContext(EmscriptenGLContext& surface.GetNativeHandle(&nativeHandle, sizeof(nativeHandle)); /* Create drawable surface */ - surface_ = eglCreateWindowSurface(display_, context.GetEGLConfig(), nativeHandle.window, nullptr); + //surface_ = eglCreateWindowSurface(display_, context.GetEGLConfig(), nativeHandle.window, nullptr); + if (!surface_) throw std::runtime_error("eglCreateWindowSurface failed"); } diff --git a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.h b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.h index 7a1fb79600..045c9cd894 100644 --- a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.h +++ b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.h @@ -1,5 +1,5 @@ /* - * AndroidGLSwapChainContext.h + * EmscriptenGLSwapChainContext.h * * Copyright (c) 2015 Lukas Hermanns. All rights reserved. * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). From 169e3666967f9122ecdbf60e4b883fb90b6e539f Mon Sep 17 00:00:00 2001 From: Victor Stanchevici Date: Mon, 18 Mar 2024 22:55:04 +0200 Subject: [PATCH 03/15] Adding WebGL for Emscripten --- .../Emscripten/EmscriptenNativeHandle.h | 5 +- .../Emscripten/EmscriptenNativeHandle.h | 4 +- .../Emscripten/EmscriptenGLContext.cpp | 99 +++++-------------- .../Platform/Emscripten/EmscriptenGLContext.h | 29 ++---- .../EmscriptenGLSwapChainContext.cpp | 36 ++++--- .../Emscripten/EmscriptenGLSwapChainContext.h | 7 +- 6 files changed, 57 insertions(+), 123 deletions(-) diff --git a/include/LLGL/Backend/OpenGL/Emscripten/EmscriptenNativeHandle.h b/include/LLGL/Backend/OpenGL/Emscripten/EmscriptenNativeHandle.h index 892aa898e1..01c52f730b 100644 --- a/include/LLGL/Backend/OpenGL/Emscripten/EmscriptenNativeHandle.h +++ b/include/LLGL/Backend/OpenGL/Emscripten/EmscriptenNativeHandle.h @@ -9,6 +9,9 @@ #define LLGL_OPENGL_EMSCRIPTEN_NATIVE_HANDLE_H +#include + + namespace LLGL { @@ -23,7 +26,7 @@ namespace OpenGL */ struct RenderSystemNativeHandle { - int context; + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context; }; diff --git a/include/LLGL/Platform/Emscripten/EmscriptenNativeHandle.h b/include/LLGL/Platform/Emscripten/EmscriptenNativeHandle.h index 1df80371f4..cf8f55d878 100644 --- a/include/LLGL/Platform/Emscripten/EmscriptenNativeHandle.h +++ b/include/LLGL/Platform/Emscripten/EmscriptenNativeHandle.h @@ -17,8 +17,8 @@ namespace LLGL //! Emscripten native handle structure. struct NativeHandle { - //! HTML canvas object. - emscripten::val canvas; + //! CSS selector of canvas object. + std::string canvas; }; diff --git a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.cpp b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.cpp index 7236f7000b..68d6388894 100644 --- a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.cpp +++ b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.cpp @@ -44,8 +44,6 @@ EmscriptenGLContext::EmscriptenGLContext( const RendererConfigurationOpenGL& profile, Surface& surface, EmscriptenGLContext* sharedContext) -: - display_ { eglGetDisplay(EGL_DEFAULT_DISPLAY) } { CreateContext(pixelFormat, profile, sharedContext); } @@ -57,6 +55,7 @@ EmscriptenGLContext::~EmscriptenGLContext() int EmscriptenGLContext::GetSamples() const { + int samples_ = 4; return samples_; } @@ -65,7 +64,7 @@ bool EmscriptenGLContext::GetNativeHandle(void* nativeHandle, std::size_t native if (nativeHandle != nullptr && nativeHandleSize == sizeof(OpenGL::RenderSystemNativeHandle)) { auto* nativeHandleGL = reinterpret_cast(nativeHandle); - //nativeHandleGL->context = context_; + nativeHandleGL->context = context_; return true; } return false; @@ -78,88 +77,36 @@ bool EmscriptenGLContext::GetNativeHandle(void* nativeHandle, std::size_t native bool EmscriptenGLContext::SetSwapInterval(int interval) { - return (eglSwapInterval(display_, interval) == EGL_TRUE); + return true; } -bool EmscriptenGLContext::SelectConfig(const GLPixelFormat& pixelFormat) +void EmscriptenGLContext::CreateContext(const GLPixelFormat& pixelFormat, const RendererConfigurationOpenGL& profile, EmscriptenGLContext* sharedContext) { - /* Look for a framebuffer configuration; reduce samples if necessary */ - for (samples_ = pixelFormat.samples; samples_ > 1; --samples_) - { - /* Initialize framebuffer configuration */ - const EGLint attribs[] = - { - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - //EGL_ALPHA_SIZE, 8, - EGL_DEPTH_SIZE, pixelFormat.depthBits, - EGL_STENCIL_SIZE, pixelFormat.stencilBits, - EGL_SAMPLE_BUFFERS, (samples_ > 1 ? 1 : 0), - EGL_SAMPLES, (samples_ > 1 ? samples_ : 1), - EGL_NONE - }; - - /* Choose configuration */ - EGLint numConfigs = 0; - EGLBoolean success = eglChooseConfig(display_, attribs, &config_, 1, &numConfigs); - - /* Reduce number of sample if configuration failed */ - if (success == EGL_TRUE && numConfigs >= 1) - { - SetDefaultColorFormat(); - DeduceDepthStencilFormat(pixelFormat.depthBits, pixelFormat.stencilBits); - return true; - } - } - - /* No suitable configuration found */ - return false; -} - -void EmscriptenGLContext::CreateContext( - const GLPixelFormat& pixelFormat, - const RendererConfigurationOpenGL& profile, - EmscriptenGLContext* sharedContext) -{ - /* Initialize EGL display connection (ignore major/minor output parameters) */ - if (!eglInitialize(display_, nullptr, nullptr)) - throw std::runtime_error("eglInitialize failed"); - - /* Select EGL context configuration for pixel format */ - if (!SelectConfig(pixelFormat)) - throw std::runtime_error("eglChooseConfig failed"); - - /* Set up EGL profile attributes */ - EGLint major = 3, minor = 0; + EmscriptenWebGLContextAttributes attrs; + emscripten_webgl_init_context_attributes(&attrs); + attrs.majorVersion = 2; + attrs.minorVersion = 0; + attrs.alpha = false; + attrs.depth = false; + attrs.stencil = false; + attrs.antialias = false; + attrs.premultipliedAlpha = true; + attrs.preserveDrawingBuffer = false; + attrs.explicitSwapControl = 0; + //attrs.preferLowPowerToHighPerformance = false; + attrs.failIfMajorPerformanceCaveat = false; + attrs.enableExtensionsByDefault = true; + + + context_ = emscripten_webgl_create_context("!canvas", &attrs); - if (!(profile.majorVersion == 0 && profile.minorVersion == 0)) - { - major = profile.majorVersion; - minor = profile.minorVersion; - } - - const EGLint contextAttribs[] = - { - EGL_CONTEXT_MAJOR_VERSION, major, - EGL_CONTEXT_MINOR_VERSION, minor, - #ifdef LLGL_DEBUG - EGL_CONTEXT_OPENGL_DEBUG, EGL_TRUE, - EGL_CONTEXT_OPENGL_ROBUST_ACCESS, EGL_TRUE, - #endif - EGL_NONE - }; - - /* Create EGL context with optional shared EGL context */ - EGLContext sharedEGLContext = (sharedContext != nullptr ? sharedContext->context_ : EGL_NO_CONTEXT); - context_ = eglCreateContext(display_, config_, sharedEGLContext, contextAttribs); if (!context_) - throw std::runtime_error("eglCreateContext failed"); + throw std::runtime_error("emscripten_webgl_create_context failed"); } void EmscriptenGLContext::DeleteContext() { - eglDestroyContext(display_, context_); + //destroy context_ } diff --git a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.h b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.h index 00ba22cfe4..69399029a9 100644 --- a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.h +++ b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.h @@ -11,8 +11,8 @@ #include "../GLContext.h" #include "../../OpenGL.h" -#include - +#include +#include namespace LLGL { @@ -38,30 +38,16 @@ class EmscriptenGLContext : public GLContext public: - // Returns the native EGL display. - inline ::EGLDisplay GetEGLDisplay() const - { - return display_; - } - - // Returns the native EGL context. - inline ::EGLContext GetEGLContext() const + // Returns the native WebGL context. + inline EMSCRIPTEN_WEBGL_CONTEXT_HANDLE GetWebGLContext() const { return context_; } - // Returns the native EGL configuration. - inline ::EGLConfig GetEGLConfig() const - { - return config_; - } - private: - + bool SetSwapInterval(int interval) override; - bool SelectConfig(const GLPixelFormat& pixelFormat); - void CreateContext( const GLPixelFormat& pixelFormat, const RendererConfigurationOpenGL& profile, @@ -71,10 +57,7 @@ class EmscriptenGLContext : public GLContext private: - ::EGLDisplay display_ = nullptr; - ::EGLContext context_ = nullptr; - ::EGLConfig config_ = nullptr; - int samples_ = 1; + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context_ = 0; }; diff --git a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.cpp b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.cpp index 0ff7f60a41..dcbb291b37 100644 --- a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.cpp +++ b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.cpp @@ -6,11 +6,9 @@ */ #include "EmscriptenGLSwapChainContext.h" -#include "EmscriptenGLContext.h" #include "../../../../Core/CoreUtils.h" #include - namespace LLGL { @@ -35,29 +33,24 @@ bool GLSwapChainContext::MakeCurrentUnchecked(GLSwapChainContext* context) */ EmscriptenGLSwapChainContext::EmscriptenGLSwapChainContext(EmscriptenGLContext& context, Surface& surface) : - GLSwapChainContext { context }, - display_ { context.GetEGLDisplay() }, - context_ { context.GetEGLContext() } + GLSwapChainContext { context } { /* Get native surface handle */ NativeHandle nativeHandle; surface.GetNativeHandle(&nativeHandle, sizeof(nativeHandle)); - - /* Create drawable surface */ - //surface_ = eglCreateWindowSurface(display_, context.GetEGLConfig(), nativeHandle.window, nullptr); - - if (!surface_) - throw std::runtime_error("eglCreateWindowSurface failed"); + + if (!context.GetWebGLContext()) + throw std::runtime_error("GetWebGLContext failed"); } EmscriptenGLSwapChainContext::~EmscriptenGLSwapChainContext() { - eglDestroySurface(display_, surface_); + //eglDestroySurface(display_, surface_); } bool EmscriptenGLSwapChainContext::SwapBuffers() { - eglSwapBuffers(display_, surface_); + //empty return true; } @@ -68,10 +61,21 @@ void EmscriptenGLSwapChainContext::Resize(const Extent2D& resolution) bool EmscriptenGLSwapChainContext::MakeCurrentEGLContext(EmscriptenGLSwapChainContext* context) { - if (context) - return eglMakeCurrent(context->display_, context->surface_, context->surface_, context->context_); + EMSCRIPTEN_RESULT res = emscripten_webgl_make_context_current(context->context_); + + if (res == EMSCRIPTEN_RESULT_SUCCESS) + { + assert(emscripten_webgl_get_current_context() == context->GetGLContext()); + + int width, height, fs = 0; + emscripten_get_canvas_element_size("#canvas", &width, &height); + //printf("width:%d, height:%d\n", width, height); + //SetViewportSize((ndf32)width, (ndf32)height); + } else - return eglMakeCurrent(eglGetDisplay(EGL_DEFAULT_DISPLAY), EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + throw std::runtime_error("emscripten_webgl_make_context_current failed"); + + return true; } diff --git a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.h b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.h index 045c9cd894..22f323155c 100644 --- a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.h +++ b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.h @@ -11,8 +11,7 @@ #include "../GLSwapChainContext.h" #include "../../OpenGL.h" -#include - +#include "EmscriptenGLContext.h" namespace LLGL { @@ -38,9 +37,7 @@ class EmscriptenGLSwapChainContext final : public GLSwapChainContext private: - ::EGLDisplay display_ = nullptr; - ::EGLContext context_ = nullptr; - ::EGLSurface surface_ = nullptr; + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context_ = 0; }; From 5c69511849ee4d63602f3fd7b34dec8b1927df07 Mon Sep 17 00:00:00 2001 From: Victor Stanchevici Date: Tue, 19 Mar 2024 18:29:41 +0200 Subject: [PATCH 04/15] Update --- .../Emscripten/EmscriptenGLContext.cpp | 5 ++++- .../EmscriptenGLSwapChainContext.cpp | 18 +++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.cpp b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.cpp index 68d6388894..9568733020 100644 --- a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.cpp +++ b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.cpp @@ -93,15 +93,18 @@ void EmscriptenGLContext::CreateContext(const GLPixelFormat& pixelFormat, const attrs.premultipliedAlpha = true; attrs.preserveDrawingBuffer = false; attrs.explicitSwapControl = 0; + attrs.enableExtensionsByDefault = true; //attrs.preferLowPowerToHighPerformance = false; attrs.failIfMajorPerformanceCaveat = false; attrs.enableExtensionsByDefault = true; - context_ = emscripten_webgl_create_context("!canvas", &attrs); + context_ = emscripten_webgl_create_context("#mycanvas", &attrs); if (!context_) throw std::runtime_error("emscripten_webgl_create_context failed"); + + EMSCRIPTEN_RESULT res = emscripten_webgl_make_context_current(context_); } void EmscriptenGLContext::DeleteContext() diff --git a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.cpp b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.cpp index dcbb291b37..5723ef023c 100644 --- a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.cpp +++ b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.cpp @@ -50,32 +50,36 @@ EmscriptenGLSwapChainContext::~EmscriptenGLSwapChainContext() bool EmscriptenGLSwapChainContext::SwapBuffers() { - //empty + // do nothing return true; } void EmscriptenGLSwapChainContext::Resize(const Extent2D& resolution) { - // dummy + // do nothing (WebGL context does not need to be resized) } bool EmscriptenGLSwapChainContext::MakeCurrentEGLContext(EmscriptenGLSwapChainContext* context) { + return true; EMSCRIPTEN_RESULT res = emscripten_webgl_make_context_current(context->context_); if (res == EMSCRIPTEN_RESULT_SUCCESS) { - assert(emscripten_webgl_get_current_context() == context->GetGLContext()); + //assert(emscripten_webgl_get_current_context() == context->GetGLContext()); int width, height, fs = 0; - emscripten_get_canvas_element_size("#canvas", &width, &height); - //printf("width:%d, height:%d\n", width, height); + emscripten_get_canvas_element_size("#mycanvas", &width, &height); + printf("width:%d, height:%d\n", width, height); //SetViewportSize((ndf32)width, (ndf32)height); + + return true; } else + { throw std::runtime_error("emscripten_webgl_make_context_current failed"); - - return true; + return false; + } } From 73058826fea65a0504bc7980c4cc455cad4c80ab Mon Sep 17 00:00:00 2001 From: Laura Hermanns Date: Tue, 13 Aug 2024 20:11:47 -0400 Subject: [PATCH 05/15] [Wasm] Make GL backend ready for Emscripten. - Added WebGL profile to OpenGL backend; Can be loaded for Emscripten as "WebGL" backend. - Fixed various compile issues with Emscripten. - Deprecated versioned identifiers for RendererID::OpenGLES* and replaced it with OpenGLES, WebGL, and WebGPU identifiers. --- CMakeLists.txt | 20 +- examples/Cpp/ExampleBase/ExampleBase.cpp | 4 +- examples/Cpp/ShadowMapping/Example.cpp | 2 +- include/LLGL/RenderSystemFlags.h | 15 +- .../Platform/Emscripten/EmscriptenDebug.cpp | 5 + .../Platform/Emscripten/EmscriptenDisplay.cpp | 6 +- .../Platform/Emscripten/EmscriptenTimer.cpp | 2 +- .../Platform/Emscripten/EmscriptenWindow.cpp | 4 +- sources/Platform/Emscripten/MapKey.cpp | 3 +- sources/Platform/Emscripten/MapKey.h | 1 - sources/Renderer/OpenGL/Buffer/GLBuffer.cpp | 2 +- sources/Renderer/OpenGL/CMakeLists.txt | 97 ++++--- sources/Renderer/OpenGL/Ext/GLExtensions.h | 6 +- sources/Renderer/OpenGL/GLCore.cpp | 6 +- .../OpenGL/GLCoreProfile/GLCoreProfile.cpp | 5 + .../GLESProfile/GLESExtensionLoader.cpp | 2 +- .../OpenGL/GLESProfile/GLESExtensionsDecl.inl | 2 +- .../OpenGL/GLESProfile/GLESProfile.cpp | 5 + .../Renderer/OpenGL/GLESProfile/OpenGLES.h | 5 - sources/Renderer/OpenGL/GLModuleInterface.cpp | 4 +- sources/Renderer/OpenGL/GLProfile.h | 11 +- sources/Renderer/OpenGL/GLTypes.cpp | 108 ++++---- sources/Renderer/OpenGL/OpenGL.h | 26 +- .../Emscripten/EmscriptenGLContext.cpp | 33 ++- .../EmscriptenGLSwapChainContext.cpp | 18 +- .../Emscripten/EmscriptenGLSwapChainContext.h | 1 + .../OpenGL/Shader/GLShaderProgram.cpp | 6 +- sources/Renderer/OpenGL/Texture/GLTexture.cpp | 2 +- sources/Renderer/OpenGL/Texture/GLTexture.h | 2 +- sources/Renderer/OpenGL/WebGLProfile/WebGL.h | 28 ++ .../WebGLProfile/WebGLExtensionLoader.cpp | 145 +++++++++++ .../OpenGL/WebGLProfile/WebGLProfile.cpp | 123 +++++++++ .../OpenGL/WebGLProfile/WebGLProfileCaps.cpp | 241 ++++++++++++++++++ .../OpenGL/WebGLProfile/WebGLProfileTypes.h | 112 ++++++++ sources/Renderer/StaticModuleInterface.cpp | 19 ++ 35 files changed, 910 insertions(+), 161 deletions(-) create mode 100644 sources/Renderer/OpenGL/WebGLProfile/WebGL.h create mode 100644 sources/Renderer/OpenGL/WebGLProfile/WebGLExtensionLoader.cpp create mode 100644 sources/Renderer/OpenGL/WebGLProfile/WebGLProfile.cpp create mode 100644 sources/Renderer/OpenGL/WebGLProfile/WebGLProfileCaps.cpp create mode 100644 sources/Renderer/OpenGL/WebGLProfile/WebGLProfileTypes.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f6a815e274..f2f5ab9f66 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -410,7 +410,9 @@ option(LLGL_BUILD_EXAMPLES "Include example projects" OFF) option(LLGL_BUILD_RENDERER_NULL "Include Null renderer project" ON) if(NOT LLGL_UWP_PLATFORM) - if(LLGL_MOBILE_PLATFORM OR EMSCRIPTEN) + if(EMSCRIPTEN) + option(LLGL_BUILD_RENDERER_WEBGL "Include WebGL renderer project" ON) + elseif(LLGL_MOBILE_PLATFORM) option(LLGL_BUILD_RENDERER_OPENGLES3 "Include OpenGLES 3 renderer project" ON) else() option(LLGL_BUILD_RENDERER_OPENGL "Include OpenGL renderer project" ON) @@ -485,9 +487,13 @@ else() set(SUMMARY_TARGET_ARCH "x86") endif() -if(EMSCRIPTEN) +if(EMSCRIPTEN) add_compile_options("SHELL:-s USE_PTHREADS") - add_link_options("SHELL:-s USE_PTHREADS") + add_link_options("SHELL:-s USE_PTHREADS") + + # LLGL needs at least WebGL 2.0 + add_link_options("SHELL:-s MIN_WEBGL_VERSION=2") + add_link_options("SHELL:-s MAX_WEBGL_VERSION=2") endif() @@ -690,7 +696,7 @@ elseif(APPLE) target_link_libraries(LLGL "-framework CoreVideo") endif() elseif(EMSCRIPTEN) - ### + target_link_libraries(LLGL embind) elseif(UNIX) target_link_libraries(LLGL X11 pthread Xrandr) #elseif(LLGL_UWP_PLATFORM) @@ -709,7 +715,7 @@ if(LLGL_BUILD_RENDERER_NULL) add_subdirectory(sources/Renderer/Null) endif() -if(LLGL_BUILD_RENDERER_OPENGL OR LLGL_BUILD_RENDERER_OPENGLES3) +if(LLGL_BUILD_RENDERER_OPENGL OR LLGL_BUILD_RENDERER_OPENGLES3 OR LLGL_BUILD_RENDERER_WEBGL) add_subdirectory(sources/Renderer/OpenGL) endif() @@ -799,6 +805,10 @@ if(LLGL_BUILD_RENDERER_OPENGLES3) message(STATUS "Build Renderer: ${LLGL_GL_ENABLE_OPENGLES}") endif() +if(LLGL_BUILD_RENDERER_WEBGL) + message(STATUS "Build Renderer: WebGL") +endif() + if(LLGL_BUILD_RENDERER_VULKAN) message(STATUS "Build Renderer: Vulkan") endif() diff --git a/examples/Cpp/ExampleBase/ExampleBase.cpp b/examples/Cpp/ExampleBase/ExampleBase.cpp index 4fce55ad66..b237bb10ff 100644 --- a/examples/Cpp/ExampleBase/ExampleBase.cpp +++ b/examples/Cpp/ExampleBase/ExampleBase.cpp @@ -152,6 +152,8 @@ static constexpr const char* GetDefaultRendererModule() return "Metal"; #elif defined LLGL_OS_ANDROID return "OpenGLES3"; + #elif defined LLGL_OS_EMSCRIPTEN + return "WebGL"; #else return "OpenGL"; #endif @@ -941,7 +943,7 @@ bool ExampleBase::IsOpenGL() const return ( renderer->GetRendererID() == LLGL::RendererID::OpenGL || - renderer->GetRendererID() == LLGL::RendererID::OpenGLES3 + renderer->GetRendererID() == LLGL::RendererID::OpenGLES ); } diff --git a/examples/Cpp/ShadowMapping/Example.cpp b/examples/Cpp/ShadowMapping/Example.cpp index 7bc6ebb106..872ed564c7 100644 --- a/examples/Cpp/ShadowMapping/Example.cpp +++ b/examples/Cpp/ShadowMapping/Example.cpp @@ -164,7 +164,7 @@ class Example_ShadowMapping : public ExampleBase LLGL::SamplerDescriptor shadowSamplerDesc; { // Clamp-to-border sampler address mode requires GLES 3.2, so use standard clamp mode in case hardware only supports GLES 3.0 - if (renderer->GetRendererID() == LLGL::RendererID::OpenGLES3) + if (renderer->GetRendererID() == LLGL::RendererID::OpenGLES) { shadowSamplerDesc.addressModeU = LLGL::SamplerAddressMode::Clamp; shadowSamplerDesc.addressModeV = LLGL::SamplerAddressMode::Clamp; diff --git a/include/LLGL/RenderSystemFlags.h b/include/LLGL/RenderSystemFlags.h index d06392ab20..91e2e3bd80 100644 --- a/include/LLGL/RenderSystemFlags.h +++ b/include/LLGL/RenderSystemFlags.h @@ -240,9 +240,9 @@ struct RendererID static constexpr int Null = 0x00000001; //!< ID number for a Null renderer. This renderer does not render anything but provides the same interface for debugging purposes. static constexpr int OpenGL = 0x00000002; //!< ID number for an OpenGL renderer. - static constexpr int OpenGLES1 = 0x00000003; //!< ID number for an OpenGL ES 1 renderer. - static constexpr int OpenGLES2 = 0x00000004; //!< ID number for an OpenGL ES 2 renderer. - static constexpr int OpenGLES3 = 0x00000005; //!< ID number for an OpenGL ES 3 renderer. + static constexpr int OpenGLES = 0x00000003; //!< ID number for an OpenGL ES renderer. + static constexpr int WebGL = 0x00000004; //!< ID number for a WebGL renderer. + static constexpr int WebGPU = 0x00000005; //!< ID number for a WebGPU renderer. static constexpr int Direct3D9 = 0x00000006; //!< ID number for a Direct3D 9 renderer. static constexpr int Direct3D10 = 0x00000007; //!< ID number for a Direct3D 10 renderer. static constexpr int Direct3D11 = 0x00000008; //!< ID number for a Direct3D 11 renderer. @@ -250,6 +250,15 @@ struct RendererID static constexpr int Vulkan = 0x0000000A; //!< ID number for a Vulkan renderer. static constexpr int Metal = 0x0000000B; //!< ID number for a Metal renderer. + LLGL_DEPRECATED("LLGL::RendererID::OpenGLES1 is deprecated since 0.04b; Use LLGL::RendererID::OpenGLES instead!", "OpenGLES") + static constexpr int OpenGLES1 = RendererID::OpenGLES; + + LLGL_DEPRECATED("LLGL::RendererID::OpenGLES2 is deprecated since 0.04b; Use LLGL::RendererID::OpenGLES instead!", "OpenGLES") + static constexpr int OpenGLES2 = RendererID::OpenGLES; + + LLGL_DEPRECATED("LLGL::RendererID::OpenGLES3 is deprecated since 0.04b; Use LLGL::RendererID::OpenGLES instead!", "OpenGLES") + static constexpr int OpenGLES3 = RendererID::OpenGLES; + static constexpr int Reserved = 0x000000FF; //!< Highest ID number for reserved future renderers. Value is 0x000000ff. }; diff --git a/sources/Platform/Emscripten/EmscriptenDebug.cpp b/sources/Platform/Emscripten/EmscriptenDebug.cpp index 2655a054f6..b0dc9de5b4 100644 --- a/sources/Platform/Emscripten/EmscriptenDebug.cpp +++ b/sources/Platform/Emscripten/EmscriptenDebug.cpp @@ -18,6 +18,11 @@ LLGL_EXPORT void DebugPuts(const char* text) ::fprintf(stderr, "%s\n", text); } +LLGL_EXPORT UTF8String DebugStackTrace(unsigned firstStackFrame, unsigned maxNumStackFrames) +{ + return {}; //TODO +} + } // /namespace LLGL diff --git a/sources/Platform/Emscripten/EmscriptenDisplay.cpp b/sources/Platform/Emscripten/EmscriptenDisplay.cpp index 4af200c43f..abb98d9c41 100644 --- a/sources/Platform/Emscripten/EmscriptenDisplay.cpp +++ b/sources/Platform/Emscripten/EmscriptenDisplay.cpp @@ -1,5 +1,5 @@ /* - * LinuxDisplay.h + * EmscriptenDisplay.cpp * * Copyright (c) 2015 Lukas Hermanns. All rights reserved. * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). @@ -73,7 +73,7 @@ bool Display::SetCursorPosition(const Offset2D& position) Offset2D Display::GetCursorPosition() { Offset2D rootPosition = { 0, 0 }; - Offset2D childPosition = { 0, 0 }; + //Offset2D childPosition = { 0, 0 }; return rootPosition; } @@ -83,7 +83,7 @@ Offset2D Display::GetCursorPosition() */ EmscriptenDisplay::EmscriptenDisplay(int screenIndex) : - screen_ { screenIndex } + screen_ { screenIndex } { } diff --git a/sources/Platform/Emscripten/EmscriptenTimer.cpp b/sources/Platform/Emscripten/EmscriptenTimer.cpp index 321917a567..de7d1c3b89 100644 --- a/sources/Platform/Emscripten/EmscriptenTimer.cpp +++ b/sources/Platform/Emscripten/EmscriptenTimer.cpp @@ -1,5 +1,5 @@ /* - * LinuxTimer.cpp + * EmscriptenTimer.cpp * * Copyright (c) 2015 Lukas Hermanns. All rights reserved. * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). diff --git a/sources/Platform/Emscripten/EmscriptenWindow.cpp b/sources/Platform/Emscripten/EmscriptenWindow.cpp index 2d347f6ec0..ea0ed71efe 100644 --- a/sources/Platform/Emscripten/EmscriptenWindow.cpp +++ b/sources/Platform/Emscripten/EmscriptenWindow.cpp @@ -23,9 +23,7 @@ namespace LLGL bool Surface::ProcessEvents() { - - - return true; + return true; // dummy - handled by web browser } diff --git a/sources/Platform/Emscripten/MapKey.cpp b/sources/Platform/Emscripten/MapKey.cpp index 0f95ac6cd2..5153e51760 100644 --- a/sources/Platform/Emscripten/MapKey.cpp +++ b/sources/Platform/Emscripten/MapKey.cpp @@ -1,5 +1,5 @@ /* - * MapKey.cpp (Linux) + * MapKey.cpp (Emscripten) * * Copyright (c) 2015 Lukas Hermanns. All rights reserved. * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). @@ -7,6 +7,7 @@ #include "MapKey.h" #include +#include #include diff --git a/sources/Platform/Emscripten/MapKey.h b/sources/Platform/Emscripten/MapKey.h index deb95033f0..c65536a845 100644 --- a/sources/Platform/Emscripten/MapKey.h +++ b/sources/Platform/Emscripten/MapKey.h @@ -10,7 +10,6 @@ #include -#include namespace LLGL diff --git a/sources/Renderer/OpenGL/Buffer/GLBuffer.cpp b/sources/Renderer/OpenGL/Buffer/GLBuffer.cpp index eca74c3dae..20512c49f4 100644 --- a/sources/Renderer/OpenGL/Buffer/GLBuffer.cpp +++ b/sources/Renderer/OpenGL/Buffer/GLBuffer.cpp @@ -307,7 +307,7 @@ void GLBuffer::UnmapBuffer() #endif // /GL_ARB_direct_state_access { GLStateManager::Get().BindGLBuffer(*this); - glUnmapBuffer(GetGLTarget()); + GLProfile::UnmapBuffer(GetGLTarget()); } } diff --git a/sources/Renderer/OpenGL/CMakeLists.txt b/sources/Renderer/OpenGL/CMakeLists.txt index 043d90cd57..e269e6a6a6 100644 --- a/sources/Renderer/OpenGL/CMakeLists.txt +++ b/sources/Renderer/OpenGL/CMakeLists.txt @@ -62,6 +62,7 @@ find_source_files(FilesRendererGLShader CXX ${PROJECT_SOURCE_DIR}/Shader find_source_files(FilesRendererGLTexture CXX ${PROJECT_SOURCE_DIR}/Texture) find_source_files(FilesRendererGLCoreProfile CXX ${PROJECT_SOURCE_DIR}/GLCoreProfile) find_source_files(FilesRendererGLESProfile CXX ${PROJECT_SOURCE_DIR}/GLESProfile) +find_source_files(FilesRendererWebGLProfile CXX ${PROJECT_SOURCE_DIR}/WebGLProfile) find_source_files(FilesIncludeGL INC ${BACKEND_INCLUDE_DIR}/OpenGL) # Remove selected files if GL2X is disabled @@ -107,38 +108,6 @@ elseif(UNIX) endif() endif() -set( - FilesGL - ${FilesRendererGL} - ${FilesRendererGLBuffer} - ${FilesRendererGLCommand} - ${FilesRendererGLExt} - ${FilesRendererGLPlatform} - ${FilesRendererGLPlatformBase} - ${FilesRendererGLRenderState} - ${FilesRendererGLShader} - ${FilesRendererGLTexture} - ${FilesRendererGLCoreProfile} - ${FilesIncludeGL} - ${FilesIncludeGLPlatform} -) - -set( - FilesGLES3 - ${FilesRendererGL} - ${FilesRendererGLBuffer} - ${FilesRendererGLCommand} - ${FilesRendererGLExt} - ${FilesRendererGLPlatform} - ${FilesRendererGLPlatformBase} - ${FilesRendererGLRenderState} - ${FilesRendererGLES3Shader} - ${FilesRendererGLTexture} - ${FilesRendererGLESProfile} - ${FilesIncludeGL} - ${FilesIncludeGLPlatform} -) - # === Source group folders === @@ -152,6 +121,7 @@ source_group("OpenGL\\Shader" FILES ${FilesRendererGLShader}) source_group("OpenGL\\Texture" FILES ${FilesRendererGLTexture}) source_group("OpenGL\\GLCoreProfile" FILES ${FilesRendererGLCoreProfile}) source_group("OpenGL\\GLESProfile" FILES ${FilesRendererGLESProfile}) +source_group("OpenGL\\WebGLProfile" FILES ${FilesRendererWebGLProfile}) source_group("Include\\Platform" FILES ${FilesIncludeGL} ${FilesIncludeGLPlatform}) @@ -165,8 +135,53 @@ endif() # === Projects === +if(LLGL_BUILD_RENDERER_WEBGL) + # WebGL Renderer + set( + FilesWebGL + ${FilesRendererGL} + ${FilesRendererGLBuffer} + ${FilesRendererGLCommand} + ${FilesRendererGLExt} + ${FilesRendererGLPlatform} + ${FilesRendererGLPlatformBase} + ${FilesRendererGLRenderState} + ${FilesRendererGLES3Shader} + ${FilesRendererGLTexture} + ${FilesRendererWebGLProfile} + ${FilesIncludeGL} + ${FilesIncludeGLPlatform} + ) + + include("${EXTERNAL_MODULE_DIR}/FindOpenGLES3.cmake") + if(OPENGLES_FOUND) + include_directories(${OPENGLES_INCLUDE_DIR}) + add_llgl_module(LLGL_WebGL LLGL_BUILD_RENDERER_WEBGL "${FilesWebGL}") + target_link_libraries(LLGL_WebGL LLGL ${OPENGLES_LIBRARIES}) + ADD_PROJECT_DEFINE(LLGL_WebGL LLGL_WEBGL) + else() + message(FATAL_ERROR "LLGL_BUILD_RENDERER_WEBGL failed: missing OpenGLES libraries") + endif() +endif() + if(LLGL_BUILD_RENDERER_OPENGLES3) # OpenGLES Renderer + set( + FilesGLES3 + ${FilesRendererGL} + ${FilesRendererGLBuffer} + ${FilesRendererGLCommand} + ${FilesRendererGLExt} + ${FilesRendererGLPlatform} + ${FilesRendererGLPlatformBase} + ${FilesRendererGLRenderState} + ${FilesRendererGLES3Shader} + ${FilesRendererGLTexture} + ${FilesRendererGLESProfile} + ${FilesIncludeGL} + ${FilesIncludeGLPlatform} + ) + include("${EXTERNAL_MODULE_DIR}/FindOpenGLES3.cmake") if(OPENGLES_FOUND) include_directories(${OPENGLES_INCLUDE_DIR}) @@ -176,8 +191,6 @@ if(LLGL_BUILD_RENDERER_OPENGLES3) if(APPLE) target_link_libraries(LLGL_OpenGLES3 LLGL ${OPENGLES_LIBRARIES} "-framework Foundation -framework UIKit -framework QuartzCore -framework OpenGLES -framework GLKit") ADD_PROJECT_DEFINE(LLGL_OpenGLES3 GLES_SILENCE_DEPRECATION) - elseif(EMSCRIPTEN) - target_link_libraries(LLGL_OpenGLES3 LLGL ${OPENGLES_LIBRARIES}) else() target_link_libraries(LLGL_OpenGLES3 LLGL EGL GLESv3) endif() @@ -190,6 +203,22 @@ endif() if(LLGL_BUILD_RENDERER_OPENGL) # OpenGL Renderer + set( + FilesGL + ${FilesRendererGL} + ${FilesRendererGLBuffer} + ${FilesRendererGLCommand} + ${FilesRendererGLExt} + ${FilesRendererGLPlatform} + ${FilesRendererGLPlatformBase} + ${FilesRendererGLRenderState} + ${FilesRendererGLShader} + ${FilesRendererGLTexture} + ${FilesRendererGLCoreProfile} + ${FilesIncludeGL} + ${FilesIncludeGLPlatform} + ) + set(OpenGL_GL_PREFERENCE GLVND) find_package(OpenGL REQUIRED) if(OPENGL_FOUND) diff --git a/sources/Renderer/OpenGL/Ext/GLExtensions.h b/sources/Renderer/OpenGL/Ext/GLExtensions.h index a4bf1f8304..5944c5827e 100644 --- a/sources/Renderer/OpenGL/Ext/GLExtensions.h +++ b/sources/Renderer/OpenGL/Ext/GLExtensions.h @@ -9,10 +9,10 @@ #define LLGL_GL_EXTENSIONS_H -#ifdef LLGL_OPENGLES3 -# include "../GLESProfile/GLESExtensions.h" -#else +#ifdef LLGL_OPENGL # include "../GLCoreProfile/GLCoreExtensions.h" +#else +# include "../GLESProfile/GLESExtensions.h" #endif diff --git a/sources/Renderer/OpenGL/GLCore.cpp b/sources/Renderer/OpenGL/GLCore.cpp index 703cc57f47..bfc6e86a7f 100644 --- a/sources/Renderer/OpenGL/GLCore.cpp +++ b/sources/Renderer/OpenGL/GLCore.cpp @@ -177,10 +177,12 @@ int GLGetVersion() [[noreturn]] void ErrUnsupportedGLProc(const char* name) { - #ifdef LLGL_OPENGLES3 + #if defined(LLGL_OPENGL) + LLGL_TRAP("illegal use of unsupported OpenGL procedure: %s", name); + #elif defined(LLGL_OS_EMSCRIPTEN) LLGL_TRAP("illegal use of unsupported OpenGLES procedure: %s", name); #else - LLGL_TRAP("illegal use of unsupported OpenGL procedure: %s", name); + LLGL_TRAP("illegal use of unsupported WebGL procedure: %s", name); #endif } diff --git a/sources/Renderer/OpenGL/GLCoreProfile/GLCoreProfile.cpp b/sources/Renderer/OpenGL/GLCoreProfile/GLCoreProfile.cpp index de20ea9422..a3b2b557af 100644 --- a/sources/Renderer/OpenGL/GLCoreProfile/GLCoreProfile.cpp +++ b/sources/Renderer/OpenGL/GLCoreProfile/GLCoreProfile.cpp @@ -98,6 +98,11 @@ void* MapBufferRange(GLenum target, GLintptr offset, GLsizeiptr /*length*/, GLbi return reinterpret_cast(ptr + offset); } +void UnmapBuffer(GLenum target) +{ + glUnmapBuffer(target); +} + void DrawBuffer(GLenum buf) { glDrawBuffer(buf); diff --git a/sources/Renderer/OpenGL/GLESProfile/GLESExtensionLoader.cpp b/sources/Renderer/OpenGL/GLESProfile/GLESExtensionLoader.cpp index 66854a5fc2..eefc4e1e17 100644 --- a/sources/Renderer/OpenGL/GLESProfile/GLESExtensionLoader.cpp +++ b/sources/Renderer/OpenGL/GLESProfile/GLESExtensionLoader.cpp @@ -11,7 +11,7 @@ #include "GLESExtensionsProxy.h" #include "OpenGLES.h" #include "../GLCore.h" -#if defined(LLGL_OS_ANDROID) +#if defined(LLGL_OS_ANDROID) || defined(LLGL_OS_EMSCRIPTEN) # include #endif #include diff --git a/sources/Renderer/OpenGL/GLESProfile/GLESExtensionsDecl.inl b/sources/Renderer/OpenGL/GLESProfile/GLESExtensionsDecl.inl index b3404d8da6..cebf84d219 100644 --- a/sources/Renderer/OpenGL/GLESProfile/GLESExtensionsDecl.inl +++ b/sources/Renderer/OpenGL/GLESProfile/GLESExtensionsDecl.inl @@ -8,7 +8,7 @@ // THIS FILE MUST NOT HAVE A HEADER GUARD -#ifndef __APPLE__ +#if !GL_GLEXT_PROTOTYPES #ifndef DECL_GLPROC #error Missing definition of macro DECL_GLPROC(PFNTYPE, NAME, RTYPE, ARGS) diff --git a/sources/Renderer/OpenGL/GLESProfile/GLESProfile.cpp b/sources/Renderer/OpenGL/GLESProfile/GLESProfile.cpp index 3bdc302f7d..d977698e2b 100644 --- a/sources/Renderer/OpenGL/GLESProfile/GLESProfile.cpp +++ b/sources/Renderer/OpenGL/GLESProfile/GLESProfile.cpp @@ -106,6 +106,11 @@ void* MapBufferRange(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfie return glMapBufferRange(target, offset, length, access); } +void UnmapBuffer(GLenum target) +{ + glUnmapBuffer(target); +} + void DrawBuffer(GLenum buf) { glDrawBuffers(1, &buf); diff --git a/sources/Renderer/OpenGL/GLESProfile/OpenGLES.h b/sources/Renderer/OpenGL/GLESProfile/OpenGLES.h index fcce88d29a..37bd788279 100644 --- a/sources/Renderer/OpenGL/GLESProfile/OpenGLES.h +++ b/sources/Renderer/OpenGL/GLESProfile/OpenGLES.h @@ -14,11 +14,6 @@ #if defined(LLGL_OS_IOS) # include # include -#elif defined(LLGL_OS_EMSCRIPTEN) -# define GL_GLEXT_PROTOTYPES -# define EGL_EGLEXT_PROTOTYPES -# include -# include #elif defined(LLGL_OS_ANDROID) // Include all GLES 3.0 functions with static linkage # include diff --git a/sources/Renderer/OpenGL/GLModuleInterface.cpp b/sources/Renderer/OpenGL/GLModuleInterface.cpp index 329d6dc281..5c69b70963 100644 --- a/sources/Renderer/OpenGL/GLModuleInterface.cpp +++ b/sources/Renderer/OpenGL/GLModuleInterface.cpp @@ -14,8 +14,10 @@ namespace LLGL { -#ifdef LLGL_OPENGLES3 +#if defined(LLGL_OPENGLES3) # define ModuleOpenGL ModuleOpenGLES3 +#elif defined(LLGL_WEBGL) +# define ModuleOpenGL ModuleWebGL #endif namespace ModuleOpenGL diff --git a/sources/Renderer/OpenGL/GLProfile.h b/sources/Renderer/OpenGL/GLProfile.h index 803e53d118..ebbe6a56b5 100644 --- a/sources/Renderer/OpenGL/GLProfile.h +++ b/sources/Renderer/OpenGL/GLProfile.h @@ -11,8 +11,12 @@ #if defined LLGL_OPENGL # include "GLCoreProfile/GLCoreProfileTypes.h" -#elif defined LLGL_OPENGLES3 +#elif defined LLGL_OPEGNLES3 # include "GLESProfile/GLESProfileTypes.h" +#elif defined LLGL_WEBGL +# include "WebGLProfile/WebGLProfileTypes.h" +#else +# error Unknwon OpenGL backend #endif @@ -24,7 +28,7 @@ namespace GLProfile { -// Returns the renderer ID number, e.g. RendererID::OpenGL or RendererID::OpenGLES3. +// Returns the renderer ID number, e.g. RendererID::OpenGL or RendererID::OpenGLES. int GetRendererID(); // Returns the renderer module name, e.g. "OpenGL" or "OpenGLES3". @@ -63,6 +67,9 @@ void* MapBuffer(GLenum target, GLenum access); // Wrapper for glMapBufferRange; uses glMapBuffer for GL. void* MapBufferRange(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access); +// Wrapper for glUnmapBuffer. Not supported in WebGL. +void UnmapBuffer(GLenum target); + // Wrapper for glDrawBuffer; uses glDrawBuffers for GLES. void DrawBuffer(GLenum buf); diff --git a/sources/Renderer/OpenGL/GLTypes.cpp b/sources/Renderer/OpenGL/GLTypes.cpp index 400bf82ae2..73c532c0e1 100644 --- a/sources/Renderer/OpenGL/GLTypes.cpp +++ b/sources/Renderer/OpenGL/GLTypes.cpp @@ -616,59 +616,7 @@ GLenum ToPrimitiveMode(const PrimitiveTopology primitiveTopology) UniformType UnmapUniformType(const GLenum uniformType) { - #ifdef LLGL_OPENGLES3 - - switch (uniformType) - { - /* ----- Scalars/Vectors ----- */ - case GL_FLOAT: return UniformType::Float1; - case GL_FLOAT_VEC2: return UniformType::Float2; - case GL_FLOAT_VEC3: return UniformType::Float3; - case GL_FLOAT_VEC4: return UniformType::Float4; - case GL_INT: return UniformType::Int1; - case GL_INT_VEC2: return UniformType::Int2; - case GL_INT_VEC3: return UniformType::Int3; - case GL_INT_VEC4: return UniformType::Int4; - case GL_UNSIGNED_INT: return UniformType::UInt1; - case GL_UNSIGNED_INT_VEC2: return UniformType::UInt2; - case GL_UNSIGNED_INT_VEC3: return UniformType::UInt3; - case GL_UNSIGNED_INT_VEC4: return UniformType::UInt4; - case GL_BOOL: return UniformType::Bool1; - case GL_BOOL_VEC2: return UniformType::Bool2; - case GL_BOOL_VEC3: return UniformType::Bool3; - case GL_BOOL_VEC4: return UniformType::Bool4; - - /* ----- Matrices ----- */ - case GL_FLOAT_MAT2: return UniformType::Float2x2; - case GL_FLOAT_MAT3: return UniformType::Float3x3; - case GL_FLOAT_MAT4: return UniformType::Float4x4; - case GL_FLOAT_MAT2x3: return UniformType::Float2x3; - case GL_FLOAT_MAT2x4: return UniformType::Float2x4; - case GL_FLOAT_MAT3x2: return UniformType::Float3x2; - case GL_FLOAT_MAT3x4: return UniformType::Float3x4; - case GL_FLOAT_MAT4x2: return UniformType::Float4x2; - case GL_FLOAT_MAT4x3: return UniformType::Float4x3; - - /* ----- Samplers ----- */ - case GL_SAMPLER_2D: - case GL_SAMPLER_3D: - case GL_SAMPLER_CUBE: - case GL_SAMPLER_2D_SHADOW: - case GL_SAMPLER_2D_ARRAY: - case GL_SAMPLER_2D_ARRAY_SHADOW: - case GL_SAMPLER_CUBE_SHADOW: - case GL_INT_SAMPLER_2D: - case GL_INT_SAMPLER_3D: - case GL_INT_SAMPLER_CUBE: - case GL_INT_SAMPLER_2D_ARRAY: - case GL_UNSIGNED_INT_SAMPLER_2D: - case GL_UNSIGNED_INT_SAMPLER_3D: - case GL_UNSIGNED_INT_SAMPLER_CUBE: - case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: - return UniformType::Sampler; - } - - #else + #ifdef LLGL_OPENGL switch (uniformType) { @@ -793,7 +741,59 @@ UniformType UnmapUniformType(const GLenum uniformType) #endif // /__APPLE__ } - #endif // /LLGL_OPENGLES3 + #else // LLGL_OPENGL + + switch (uniformType) + { + /* ----- Scalars/Vectors ----- */ + case GL_FLOAT: return UniformType::Float1; + case GL_FLOAT_VEC2: return UniformType::Float2; + case GL_FLOAT_VEC3: return UniformType::Float3; + case GL_FLOAT_VEC4: return UniformType::Float4; + case GL_INT: return UniformType::Int1; + case GL_INT_VEC2: return UniformType::Int2; + case GL_INT_VEC3: return UniformType::Int3; + case GL_INT_VEC4: return UniformType::Int4; + case GL_UNSIGNED_INT: return UniformType::UInt1; + case GL_UNSIGNED_INT_VEC2: return UniformType::UInt2; + case GL_UNSIGNED_INT_VEC3: return UniformType::UInt3; + case GL_UNSIGNED_INT_VEC4: return UniformType::UInt4; + case GL_BOOL: return UniformType::Bool1; + case GL_BOOL_VEC2: return UniformType::Bool2; + case GL_BOOL_VEC3: return UniformType::Bool3; + case GL_BOOL_VEC4: return UniformType::Bool4; + + /* ----- Matrices ----- */ + case GL_FLOAT_MAT2: return UniformType::Float2x2; + case GL_FLOAT_MAT3: return UniformType::Float3x3; + case GL_FLOAT_MAT4: return UniformType::Float4x4; + case GL_FLOAT_MAT2x3: return UniformType::Float2x3; + case GL_FLOAT_MAT2x4: return UniformType::Float2x4; + case GL_FLOAT_MAT3x2: return UniformType::Float3x2; + case GL_FLOAT_MAT3x4: return UniformType::Float3x4; + case GL_FLOAT_MAT4x2: return UniformType::Float4x2; + case GL_FLOAT_MAT4x3: return UniformType::Float4x3; + + /* ----- Samplers ----- */ + case GL_SAMPLER_2D: + case GL_SAMPLER_3D: + case GL_SAMPLER_CUBE: + case GL_SAMPLER_2D_SHADOW: + case GL_SAMPLER_2D_ARRAY: + case GL_SAMPLER_2D_ARRAY_SHADOW: + case GL_SAMPLER_CUBE_SHADOW: + case GL_INT_SAMPLER_2D: + case GL_INT_SAMPLER_3D: + case GL_INT_SAMPLER_CUBE: + case GL_INT_SAMPLER_2D_ARRAY: + case GL_UNSIGNED_INT_SAMPLER_2D: + case GL_UNSIGNED_INT_SAMPLER_3D: + case GL_UNSIGNED_INT_SAMPLER_CUBE: + case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: + return UniformType::Sampler; + } + + #endif // /LLGL_OPENGL return UniformType::Undefined; } diff --git a/sources/Renderer/OpenGL/OpenGL.h b/sources/Renderer/OpenGL/OpenGL.h index 6a27dd0743..da0ace137f 100644 --- a/sources/Renderer/OpenGL/OpenGL.h +++ b/sources/Renderer/OpenGL/OpenGL.h @@ -13,14 +13,18 @@ # include "GLCoreProfile/OpenGLCore.h" #elif defined LLGL_OPENGLES3 # include "GLESProfile/OpenGLES.h" +#elif defined LLGL_WEBGL +# include "WebGLProfile/WebGL.h" +#else +# error Unknown OpenGL backend #endif -#if GL_ARB_draw_indirect || GL_ES_VERSION_3_1 +#if (GL_ARB_draw_indirect || GL_ES_VERSION_3_1) && !defined(LLGL_WEBGL) # define LLGL_GLEXT_DRAW_INDIRECT #endif -#if GL_ARB_draw_elements_base_vertex || GL_ES_VERSION_3_2 +#if (GL_ARB_draw_elements_base_vertex || GL_ES_VERSION_3_2) && !defined(LLGL_WEBGL) # define LLGL_GLEXT_DRAW_ELEMENTS_BASE_VERTEX #endif @@ -32,11 +36,11 @@ # define LLGL_GLEXT_MULTI_DRAW_INDIRECT #endif -#if GL_ARB_compute_shader || GL_ES_VERSION_3_1 +#if (GL_ARB_compute_shader || GL_ES_VERSION_3_1) && !defined(LLGL_WEBGL) # define LLGL_GLEXT_COMPUTE_SHADER #endif -#if GL_KHR_debug || GL_ES_VERSION_3_2 +#if (GL_KHR_debug || GL_ES_VERSION_3_2) && !defined(LLGL_WEBGL) # define LLGL_GLEXT_DEBUG 1 #endif @@ -49,27 +53,27 @@ # define LLGL_GLEXT_TRANSFORM_FEEDBACK #endif -#if GL_EXT_draw_buffers2 || GL_ES_VERSION_3_2 +#if (GL_EXT_draw_buffers2 || GL_ES_VERSION_3_2) && !defined(LLGL_WEBGL) # define LLGL_GLEXT_DRAW_BUFFERS2 #endif -#if GL_ARB_draw_buffers_blend || GL_ES_VERSION_3_2 +#if (GL_ARB_draw_buffers_blend || GL_ES_VERSION_3_2) && !defined(LLGL_WEBGL) # define LLGL_GLEXT_DRAW_BUFFERS_BLEND #endif -#if GL_ARB_tessellation_shader || GL_ES_VERSION_3_2 +#if (GL_ARB_tessellation_shader || GL_ES_VERSION_3_2) && !defined(LLGL_WEBGL) # define LLGL_GLEXT_TESSELLATION_SHADER #endif -#if GL_ARB_shader_storage_buffer_object || GL_ES_VERSION_3_1 +#if (GL_ARB_shader_storage_buffer_object || GL_ES_VERSION_3_1) && !defined(LLGL_WEBGL) # define LLGL_GLEXT_SHADER_STORAGE_BUFFER_OBJECT #endif -#if GL_ARB_program_interface_query || GL_ES_VERSION_3_1 +#if (GL_ARB_program_interface_query || GL_ES_VERSION_3_1) && !defined(LLGL_WEBGL) # define LLGL_GLEXT_PROGRAM_INTERFACE_QUERY #endif -#if defined LLGL_OPENGL || GL_ES_VERSION_3_1 +#if (defined(LLGL_OPENGL) || GL_ES_VERSION_3_1) && !defined(LLGL_WEBGL) # define LLGL_GLEXT_GET_TEX_LEVEL_PARAMETER #endif @@ -81,7 +85,7 @@ # define LLGL_SAMPLER_BORDER_COLOR #endif -#if GL_ARB_shader_image_load_store || GL_ES_VERSION_3_1 +#if (GL_ARB_shader_image_load_store || GL_ES_VERSION_3_1) && !defined(LLGL_WEBGL) # define LLGL_GLEXT_MEMORY_BARRIERS #endif diff --git a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.cpp b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.cpp index 9568733020..a8cf0cb4df 100644 --- a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.cpp +++ b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.cpp @@ -9,6 +9,7 @@ #include "../../../CheckedCast.h" #include "../../../StaticAssertions.h" #include "../../../../Core/CoreUtils.h" +#include "../../../../Core/Exception.h" #include #include @@ -84,25 +85,23 @@ void EmscriptenGLContext::CreateContext(const GLPixelFormat& pixelFormat, const { EmscriptenWebGLContextAttributes attrs; emscripten_webgl_init_context_attributes(&attrs); - attrs.majorVersion = 2; - attrs.minorVersion = 0; - attrs.alpha = false; - attrs.depth = false; - attrs.stencil = false; - attrs.antialias = false; - attrs.premultipliedAlpha = true; - attrs.preserveDrawingBuffer = false; - attrs.explicitSwapControl = 0; - attrs.enableExtensionsByDefault = true; - //attrs.preferLowPowerToHighPerformance = false; - attrs.failIfMajorPerformanceCaveat = false; - attrs.enableExtensionsByDefault = true; - - - context_ = emscripten_webgl_create_context("#mycanvas", &attrs); + attrs.majorVersion = 2; + attrs.minorVersion = 0; + attrs.alpha = (pixelFormat.colorBits > 24); + attrs.depth = (pixelFormat.depthBits > 0); + attrs.stencil = (pixelFormat.stencilBits > 0); + attrs.antialias = (pixelFormat.samples > 1); + attrs.premultipliedAlpha = true; + attrs.preserveDrawingBuffer = false; + attrs.explicitSwapControl = 0; + attrs.failIfMajorPerformanceCaveat = false; + attrs.enableExtensionsByDefault = true; + attrs.powerPreference = EM_WEBGL_POWER_PREFERENCE_DEFAULT; + + context_ = emscripten_webgl_create_context("#canvas", &attrs); if (!context_) - throw std::runtime_error("emscripten_webgl_create_context failed"); + LLGL_TRAP("emscripten_webgl_create_context failed"); EMSCRIPTEN_RESULT res = emscripten_webgl_make_context_current(context_); } diff --git a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.cpp b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.cpp index 5723ef023c..3edef6184f 100644 --- a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.cpp +++ b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.cpp @@ -7,8 +7,10 @@ #include "EmscriptenGLSwapChainContext.h" #include "../../../../Core/CoreUtils.h" +#include "../../../../Core/Exception.h" #include + namespace LLGL { @@ -33,14 +35,15 @@ bool GLSwapChainContext::MakeCurrentUnchecked(GLSwapChainContext* context) */ EmscriptenGLSwapChainContext::EmscriptenGLSwapChainContext(EmscriptenGLContext& context, Surface& surface) : - GLSwapChainContext { context } + GLSwapChainContext { context }, + context_ { context.GetWebGLContext() } { /* Get native surface handle */ - NativeHandle nativeHandle; - surface.GetNativeHandle(&nativeHandle, sizeof(nativeHandle)); + //NativeHandle nativeHandle; + //surface.GetNativeHandle(&nativeHandle, sizeof(nativeHandle)); - if (!context.GetWebGLContext()) - throw std::runtime_error("GetWebGLContext failed"); + if (!context_) + LLGL_TRAP("GetWebGLContext failed"); } EmscriptenGLSwapChainContext::~EmscriptenGLSwapChainContext() @@ -48,6 +51,11 @@ EmscriptenGLSwapChainContext::~EmscriptenGLSwapChainContext() //eglDestroySurface(display_, surface_); } +bool EmscriptenGLSwapChainContext::HasDrawable() const +{ + return true; // dummy +} + bool EmscriptenGLSwapChainContext::SwapBuffers() { // do nothing diff --git a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.h b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.h index 22f323155c..b2dbfbc085 100644 --- a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.h +++ b/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.h @@ -28,6 +28,7 @@ class EmscriptenGLSwapChainContext final : public GLSwapChainContext EmscriptenGLSwapChainContext(EmscriptenGLContext& context, Surface& surface); ~EmscriptenGLSwapChainContext(); + bool HasDrawable() const override; bool SwapBuffers() override; void Resize(const Extent2D& resolution) override; diff --git a/sources/Renderer/OpenGL/Shader/GLShaderProgram.cpp b/sources/Renderer/OpenGL/Shader/GLShaderProgram.cpp index 863ac0245d..1ef54c6c23 100644 --- a/sources/Renderer/OpenGL/Shader/GLShaderProgram.cpp +++ b/sources/Renderer/OpenGL/Shader/GLShaderProgram.cpp @@ -916,10 +916,10 @@ void GLShaderProgram::BuildProgramBinary( if (orderedShaders.fragmentShader == nullptr) { const GLchar* nullFragmentShaderSource = - #ifdef LLGL_OPENGLES3 - "#version 300 es\n" - #else + #ifdef LLGL_OPENGL "#version 330 core\n" + #else + "#version 300 es\n" #endif "void main() {}\n" ; diff --git a/sources/Renderer/OpenGL/Texture/GLTexture.cpp b/sources/Renderer/OpenGL/Texture/GLTexture.cpp index 0b1fc84353..6c9e4de3e3 100644 --- a/sources/Renderer/OpenGL/Texture/GLTexture.cpp +++ b/sources/Renderer/OpenGL/Texture/GLTexture.cpp @@ -112,7 +112,7 @@ GLTexture::GLTexture(const TextureDescriptor& desc) : } } - #ifdef LLGL_OPENGLES3 + #ifndef LLGL_GLEXT_GET_TEX_LEVEL_PARAMETER /* Store additional parameters for GLES */ extent_[0] = static_cast(desc.extent.width); extent_[1] = static_cast(desc.extent.height); diff --git a/sources/Renderer/OpenGL/Texture/GLTexture.h b/sources/Renderer/OpenGL/Texture/GLTexture.h index 42cce0640c..efbc0c8dd5 100644 --- a/sources/Renderer/OpenGL/Texture/GLTexture.h +++ b/sources/Renderer/OpenGL/Texture/GLTexture.h @@ -163,7 +163,7 @@ class GLTexture final : public Texture const bool isRenderbuffer_ = false; const GLSwizzleFormat swizzleFormat_ = GLSwizzleFormat::RGBA; // Identity texture swizzle by default - #ifdef LLGL_OPENGLES3 + #ifndef LLGL_GLEXT_GET_TEX_LEVEL_PARAMETER GLint extent_[3] = {}; GLint samples_ = 1; #endif diff --git a/sources/Renderer/OpenGL/WebGLProfile/WebGL.h b/sources/Renderer/OpenGL/WebGLProfile/WebGL.h new file mode 100644 index 0000000000..b03eeffb02 --- /dev/null +++ b/sources/Renderer/OpenGL/WebGLProfile/WebGL.h @@ -0,0 +1,28 @@ +/* + * WebGL.h + * + * Copyright (c) 2015 Lukas Hermanns. All rights reserved. + * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). + */ + +#ifndef LLGL_WEBGL_H +#define LLGL_WEBGL_H + + +#include + +#if defined(LLGL_OS_EMSCRIPTEN) +# define GL_GLEXT_PROTOTYPES 1 +# define EGL_EGLEXT_PROTOTYPES 1 +# include +# include +#else +# error Unsupported platform for WebGL +#endif + + +#endif + + + +// ================================================================================ diff --git a/sources/Renderer/OpenGL/WebGLProfile/WebGLExtensionLoader.cpp b/sources/Renderer/OpenGL/WebGLProfile/WebGLExtensionLoader.cpp new file mode 100644 index 0000000000..718f0c38b6 --- /dev/null +++ b/sources/Renderer/OpenGL/WebGLProfile/WebGLExtensionLoader.cpp @@ -0,0 +1,145 @@ +/* + * WebGLExtensionLoader.cpp + * + * Copyright (c) 2015 Lukas Hermanns. All rights reserved. + * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). + */ + +#include "../Ext/GLExtensionLoader.h" +#include "../Ext/GLExtensionRegistry.h" +#include "WebGL.h" +#include "../GLCore.h" +#if defined(LLGL_OS_EMSCRIPTEN) +# include +#endif +#include + + +namespace LLGL +{ + + +// Global member to store if the extension have already been loaded +static bool g_OpenGLESExtensionsLoaded = false; +static std::set g_supportedOpenGLESExtensions; +static std::set g_loadedOpenGLESExtensions; + +static void EnableGLESExtension(GLExt ext, const char* name) +{ + RegisterExtension(ext); + g_supportedOpenGLESExtensions.insert(name); //TODO: find better way to determine supported GLES extensions + g_loadedOpenGLESExtensions.insert(name); +} + +bool LoadSupportedOpenGLExtensions(bool isCoreProfile, bool abortOnFailure) +{ + /* Only load GL extensions once */ + if (g_OpenGLESExtensionsLoaded) + return true; + + #define ENABLE_GLEXT(NAME) \ + EnableGLESExtension(GLExt::NAME, "GL_" #NAME) + + const int version = GLGetVersion(); + + ENABLE_GLEXT(ARB_clear_buffer_object); + ENABLE_GLEXT(ARB_clear_texture); + ENABLE_GLEXT(ARB_buffer_storage); + ENABLE_GLEXT(ARB_copy_buffer); + ENABLE_GLEXT(ARB_draw_buffers); + ENABLE_GLEXT(ARB_draw_buffers_blend); + ENABLE_GLEXT(ARB_draw_elements_base_vertex); + ENABLE_GLEXT(ARB_draw_instanced); + ENABLE_GLEXT(ARB_draw_indirect); + ENABLE_GLEXT(ARB_framebuffer_object); + ENABLE_GLEXT(ARB_geometry_shader4); // no procedures + ENABLE_GLEXT(ARB_instanced_arrays); + ENABLE_GLEXT(ARB_internalformat_query); + ENABLE_GLEXT(ARB_internalformat_query2); + ENABLE_GLEXT(ARB_multitexture); + ENABLE_GLEXT(ARB_multi_draw_indirect); + ENABLE_GLEXT(ARB_occlusion_query); + ENABLE_GLEXT(ARB_pipeline_statistics_query); + ENABLE_GLEXT(ARB_polygon_offset_clamp); + ENABLE_GLEXT(ARB_sampler_objects); + ENABLE_GLEXT(ARB_seamless_cubemap_per_texture); + ENABLE_GLEXT(ARB_shader_image_load_store); + ENABLE_GLEXT(ARB_shader_objects); + ENABLE_GLEXT(ARB_shader_objects_21); + ENABLE_GLEXT(ARB_sync); + ENABLE_GLEXT(ARB_texture_compression); + ENABLE_GLEXT(ARB_texture_cube_map); // no procedures + ENABLE_GLEXT(ARB_texture_cube_map_array); // no procedures + ENABLE_GLEXT(ARB_texture_multisample); + ENABLE_GLEXT(ARB_texture_storage); + ENABLE_GLEXT(ARB_texture_storage_multisample); + ENABLE_GLEXT(ARB_timer_query); + ENABLE_GLEXT(ARB_transform_feedback3); + ENABLE_GLEXT(ARB_uniform_buffer_object); + ENABLE_GLEXT(ARB_vertex_array_object); + ENABLE_GLEXT(ARB_vertex_buffer_object); + ENABLE_GLEXT(ARB_vertex_shader); + ENABLE_GLEXT(ARB_viewport_array); + ENABLE_GLEXT(ARB_ES2_compatibility); + ENABLE_GLEXT(ARB_compatibility); + ENABLE_GLEXT(ARB_map_buffer_range); + + ENABLE_GLEXT(EXT_blend_color); + ENABLE_GLEXT(EXT_blend_equation_separate); + ENABLE_GLEXT(EXT_blend_func_separate); + ENABLE_GLEXT(EXT_blend_minmax); + ENABLE_GLEXT(EXT_copy_texture); + ENABLE_GLEXT(EXT_draw_buffers2); + ENABLE_GLEXT(EXT_gpu_shader4); + ENABLE_GLEXT(EXT_stencil_two_side); + ENABLE_GLEXT(EXT_texture3D); + ENABLE_GLEXT(EXT_texture_array); + ENABLE_GLEXT(EXT_transform_feedback); + + if (version >= 300) + { + ENABLE_GLEXT(ARB_ES3_compatibility); + ENABLE_GLEXT(ARB_get_program_binary); + ENABLE_GLEXT(ARB_shader_objects_30); + } + + if (version >= 310) + { + ENABLE_GLEXT(ARB_shader_storage_buffer_object); + ENABLE_GLEXT(ARB_program_interface_query); + ENABLE_GLEXT(ARB_compute_shader); + ENABLE_GLEXT(ARB_framebuffer_no_attachments); + } + + if (version >= 320) + { + ENABLE_GLEXT(ARB_tessellation_shader); + ENABLE_GLEXT(ARB_copy_image); + } + + #undef ENABLE_GLEXT + + return true; +} + +bool AreOpenGLExtensionsLoaded() +{ + return g_OpenGLESExtensionsLoaded; +} + +const std::set& GetSupportedOpenGLExtensions() +{ + return g_supportedOpenGLESExtensions; +} + +const std::set& GetLoadedOpenGLExtensions() +{ + return g_loadedOpenGLESExtensions; +} + + +} // /namespace LLGL + + + +// ================================================================================ diff --git a/sources/Renderer/OpenGL/WebGLProfile/WebGLProfile.cpp b/sources/Renderer/OpenGL/WebGLProfile/WebGLProfile.cpp new file mode 100644 index 0000000000..a3a669b5e6 --- /dev/null +++ b/sources/Renderer/OpenGL/WebGLProfile/WebGLProfile.cpp @@ -0,0 +1,123 @@ +/* + * WebGLProfile.cpp + * + * Copyright (c) 2015 Lukas Hermanns. All rights reserved. + * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). + */ + +#include "../GLProfile.h" +#include "../Ext/GLExtensions.h" +#include +#include + + +namespace LLGL +{ + +namespace GLProfile +{ + + +int GetRendererID() +{ + return RendererID::WebGL; +} + +const char* GetModuleName() +{ + return "WebGL"; +} + +const char* GetRendererName() +{ + return "WebGL"; +} + +const char* GetAPIName() +{ + return "WebGL"; +} + +const char* GetShadingLanguageName() +{ + return "ESSL"; +} + +GLint GetMaxViewports() +{ + return 1; +} + +void GetTexParameterInternalFormat(GLenum target, GLint* params) +{ + //TODO... +} + +void GetInternalformativ(GLenum target, GLenum internalformat, GLenum pname, GLsizei bufsize, GLint* params) +{ + //TODO +} + +void DepthRange(GLclamp_t nearVal, GLclamp_t farVal) +{ + glDepthRangef(nearVal, farVal); +} + +void ClearDepth(GLclamp_t depth) +{ + glClearDepthf(depth); +} + +void GetBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, void* data) +{ + // dummy +} + +void* MapBuffer(GLenum target, GLenum access) +{ + return nullptr; // dummy +} + +void* MapBufferRange(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access) +{ + return nullptr; // dummy +} + +void UnmapBuffer(GLenum target) +{ + // dummy +} + +void DrawBuffer(GLenum buf) +{ + glDrawBuffers(1, &buf); +} + +void FramebufferTexture1D(GLenum /*target*/, GLenum /*attachment*/, GLenum /*textarget*/, GLuint /*texture*/, GLint /*level*/) +{ + // dummy +} + +void FramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) +{ + glFramebufferTexture2D(target, attachment, textarget, texture, level); +} + +void FramebufferTexture3D(GLenum target, GLenum attachment, GLenum /*textarget*/, GLuint texture, GLint level, GLint layer) +{ + glFramebufferTextureLayer(target, attachment, texture, level, layer); +} + +void FramebufferTextureLayer(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer) +{ + glFramebufferTextureLayer(target, attachment, texture, level, layer); +} + + +} // /namespace GLProfile + +} // /namespace LLGL + + + +// ================================================================================ diff --git a/sources/Renderer/OpenGL/WebGLProfile/WebGLProfileCaps.cpp b/sources/Renderer/OpenGL/WebGLProfile/WebGLProfileCaps.cpp new file mode 100644 index 0000000000..6b84464afc --- /dev/null +++ b/sources/Renderer/OpenGL/WebGLProfile/WebGLProfileCaps.cpp @@ -0,0 +1,241 @@ +/* + * WebGLProfileCaps.cpp + * + * Copyright (c) 2015 Lukas Hermanns. All rights reserved. + * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). + */ + +#include "../GLRenderingCaps.h" +#include "../GLProfile.h" +#include "../GLTypes.h" +#include "../Ext/GLExtensions.h" +#include "../Ext/GLExtensionRegistry.h" +#include "../../../Core/CoreUtils.h" +#include +#include + + +namespace LLGL +{ + + +static std::int32_t GLGetInt(GLenum param) +{ + GLint attr = 0; + glGetIntegerv(param, &attr); + return attr; +} + +static std::uint32_t GLGetUInt(GLenum param) +{ + return static_cast(GLGetInt(param)); +}; + +static std::uint32_t GLGetUIntIndexed(GLenum param, GLuint index) +{ + GLint attr = 0; + if (HasExtension(GLExt::EXT_draw_buffers2)) + glGetIntegeri_v(param, index, &attr); + return static_cast(attr); +}; + +static float GLGetFloat(GLenum param) +{ + GLfloat attr = 0.0f; + glGetFloatv(param, &attr); + return attr; +} + +// Returns the GLES version in the ESSL version format (e.g. 200 for GLES 2.0, 320 for GLES 3.2) +static GLint GetGLESVersion() +{ + GLint major = 0, minor = 0; + glGetIntegerv(GL_MAJOR_VERSION, &major); + glGetIntegerv(GL_MINOR_VERSION, &minor); + return (major * 100 + minor * 10); +} + +static std::vector GLQueryShadingLanguages(GLint version) +{ + std::vector languages; + + /* Add supported GLSL versions */ + languages.push_back(ShadingLanguage::ESSL); + + if (version >= 200) { languages.push_back(ShadingLanguage::ESSL_100); } + if (version >= 300) { languages.push_back(ShadingLanguage::ESSL_300); } + if (version >= 310) { languages.push_back(ShadingLanguage::ESSL_310); } + if (version >= 320) { languages.push_back(ShadingLanguage::ESSL_320); } + + return languages; +} + +//TODO +static std::vector GetDefaultSupportedGLTextureFormats() +{ + return + { + Format::A8UNorm, + Format::R8UNorm, Format::R8SNorm, Format::R8UInt, Format::R8SInt, + Format::R16UNorm, Format::R16SNorm, Format::R16UInt, Format::R16SInt, Format::R16Float, + Format::R32UInt, Format::R32SInt, Format::R32Float, + Format::RG8UNorm, Format::RG8SNorm, Format::RG8UInt, Format::RG8SInt, + Format::RG16UNorm, Format::RG16SNorm, Format::RG16UInt, Format::RG16SInt, Format::RG16Float, + Format::RG32UInt, Format::RG32SInt, Format::RG32Float, + Format::RGB8UNorm, Format::RGB8SNorm, Format::RGB8UInt, Format::RGB8SInt, + Format::RGB16UNorm, Format::RGB16SNorm, Format::RGB16UInt, Format::RGB16SInt, Format::RGB16Float, + Format::RGB32UInt, Format::RGB32SInt, Format::RGB32Float, + Format::RGBA8UNorm, Format::RGBA8SNorm, Format::RGBA8UInt, Format::RGBA8SInt, + Format::RGBA16UNorm, Format::RGBA16SNorm, Format::RGBA16UInt, Format::RGBA16SInt, Format::RGBA16Float, + Format::RGBA32UInt, Format::RGBA32SInt, Format::RGBA32Float, + Format::BGRA8UNorm, Format::BGRA8UNorm_sRGB, Format::BGRA8SNorm, Format::BGRA8UInt, Format::BGRA8SInt, + Format::D16UNorm, Format::D32Float, Format::D24UNormS8UInt, Format::D32FloatS8X24UInt, + }; +} + +static void GLGetRenderingAttribs(RenderingCapabilities& caps, GLint version) +{ + /* Set fixed states for this renderer */ + caps.screenOrigin = ScreenOrigin::LowerLeft; + caps.clippingRange = ClippingRange::MinusOneToOne; + caps.shadingLanguages = GLQueryShadingLanguages(version); +} + +static void GLGetSupportedTextureFormats(std::vector& textureFormats) +{ + textureFormats = GetDefaultSupportedGLTextureFormats(); + + RemoveAllFromListIf( + textureFormats, + [](Format format) -> bool + { + if (auto internalformat = GLTypes::MapOrZero(format)) + { + GLint supported = 0; + GLProfile::GetInternalformativ(GL_TEXTURE_2D, internalformat, GL_INTERNALFORMAT_SUPPORTED, 1, &supported); + return (supported == GL_FALSE); + } + return true; + } + ); + + const auto numCompressedTexFormats = GLGetUInt(GL_NUM_COMPRESSED_TEXTURE_FORMATS); + + std::vector compressedTexFormats(numCompressedTexFormats); + glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, compressedTexFormats.data()); + + for (GLint internalFormat : compressedTexFormats) + { + const Format format = GLTypes::UnmapFormat(internalFormat); + if (format != Format::Undefined) + textureFormats.push_back(format); + } +} + +static void GLGetSupportedFeatures(RenderingFeatures& features, GLint version) +{ + /* Query all boolean capabilies by their respective OpenGL extension */ + features.hasRenderTargets = true; // GLES 2.0 + features.has3DTextures = true; // GLES 2.0 + features.hasCubeTextures = true; // GLES 2.0 + features.hasArrayTextures = true; // GLES 2.0 + features.hasCubeArrayTextures = (version >= 320); // GLES 3.2 + features.hasMultiSampleTextures = (version >= 310); // GLES 3.1 + features.hasTextureViews = false; + features.hasTextureViewSwizzle = false; + features.hasBufferViews = (version >= 300); // GLES 3.0 + features.hasConstantBuffers = (version >= 300); // GLES 3.0 + features.hasStorageBuffers = (version >= 300); // GLES 3.0 + features.hasGeometryShaders = (version >= 320); // GLES 3.2 + features.hasTessellationShaders = (version >= 320); // GLES 3.2 + features.hasTessellatorStage = (version >= 320); // GLES 3.2 + features.hasComputeShaders = (version >= 310); // GLES 3.1 + features.hasInstancing = (version >= 300); // GLES 3.0 + features.hasOffsetInstancing = false; + features.hasIndirectDrawing = (version >= 310); // GLES 3.1 + features.hasViewportArrays = false; + features.hasConservativeRasterization = false; + features.hasStreamOutputs = (version >= 300); // GLES 3.0 + features.hasLogicOp = false; + features.hasPipelineCaching = (version >= 300); // GLES 3.0 + features.hasPipelineStatistics = false; + features.hasRenderCondition = false; +} + +static void GLGetFeatureLimits(RenderingLimits& limits, GLint version) +{ + /* Determine minimal line width range for both aliased and smooth lines */ + GLfloat aliasedLineRange[2]; + glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, aliasedLineRange); + + //limits.lineWidthRange[0] = ??? + //limits.lineWidthRange[1] = ??? + + /* Query integral attributes */ + limits.maxTextureArrayLayers = GLGetUInt(GL_MAX_ARRAY_TEXTURE_LAYERS); + limits.maxColorAttachments = GLGetUInt(GL_MAX_DRAW_BUFFERS); + //limits.maxPatchVertices = ??? + //limits.maxAnisotropy = ??? + + #ifdef GL_ES_VERSION_3_1 + limits.maxComputeShaderWorkGroups[0] = GLGetUIntIndexed(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 0); + limits.maxComputeShaderWorkGroups[1] = GLGetUIntIndexed(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 1); + limits.maxComputeShaderWorkGroups[2] = GLGetUIntIndexed(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 2); + limits.maxComputeShaderWorkGroupSize[0] = GLGetUIntIndexed(GL_MAX_COMPUTE_WORK_GROUP_SIZE, 0); + limits.maxComputeShaderWorkGroupSize[1] = GLGetUIntIndexed(GL_MAX_COMPUTE_WORK_GROUP_SIZE, 1); + limits.maxComputeShaderWorkGroupSize[2] = GLGetUIntIndexed(GL_MAX_COMPUTE_WORK_GROUP_SIZE, 2); + #endif + + limits.minConstantBufferAlignment = GLGetUInt(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT); + + #ifdef GL_ES_VERSION_3_1 + limits.minSampledBufferAlignment = GLGetUInt(GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT); + limits.minStorageBufferAlignment = limits.minSampledBufferAlignment; // Use SSBO for both sampled and storage buffers + #endif + + /* Query viewport limits */ + limits.maxViewports = 1; + + GLint maxViewportDims[2]; + glGetIntegerv(GL_MAX_VIEWPORT_DIMS, maxViewportDims); + limits.maxViewportSize[0] = static_cast(maxViewportDims[0]); + limits.maxViewportSize[1] = static_cast(maxViewportDims[1]); + + /* Determine maximum buffer size to maximum value for (used in 'glBufferData') */ + limits.maxBufferSize = static_cast(std::numeric_limits::max()); + limits.maxConstantBufferSize = static_cast(GLGetUInt(GL_MAX_UNIFORM_BLOCK_SIZE)); + + /* Presume that at least one stream-output is supported */ + limits.maxStreamOutputs = 1u; + + #ifdef GL_ES_VERSION_3_2 + /* Determine tessellation limits */ + limits.maxTessFactor = GLGetUInt(GL_MAX_TESS_GEN_LEVEL); + #endif +} + +static void GLGetTextureLimits(const RenderingFeatures& features, RenderingLimits& limits, GLint version) +{ + /* No proxy textures in GLES, so rely on glGet*() functions */ + limits.max1DTextureSize = GLGetUInt(GL_MAX_TEXTURE_SIZE); + limits.max2DTextureSize = limits.max1DTextureSize; + limits.max3DTextureSize = GLGetUInt(GL_MAX_3D_TEXTURE_SIZE); + limits.maxCubeTextureSize = GLGetUInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE); +} + +void GLQueryRenderingCaps(RenderingCapabilities& caps) +{ + const GLint version = GetGLESVersion(); + GLGetRenderingAttribs(caps, version); + GLGetSupportedTextureFormats(caps.textureFormats); + GLGetSupportedFeatures(caps.features, version); + GLGetFeatureLimits(caps.limits, version); + GLGetTextureLimits(caps.features, caps.limits, version); +} + + +} // /namespace LLGL + + + +// ================================================================================ diff --git a/sources/Renderer/OpenGL/WebGLProfile/WebGLProfileTypes.h b/sources/Renderer/OpenGL/WebGLProfile/WebGLProfileTypes.h new file mode 100644 index 0000000000..69026e105c --- /dev/null +++ b/sources/Renderer/OpenGL/WebGLProfile/WebGLProfileTypes.h @@ -0,0 +1,112 @@ +/* + * WebGLProfileTypyes.h + * + * Copyright (c) 2015 Lukas Hermanns. All rights reserved. + * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). + */ + +#ifndef LLGL_WEBGL_PROFILE_TYPES_H +#define LLGL_WEBGL_PROFILE_TYPES_H + + +#include "WebGL.h" + + +namespace LLGL +{ + + +#ifndef GL_READ_ONLY +#define GL_READ_ONLY 0x88B8 // for wrappers only +#endif + +#ifndef GL_WRITE_ONLY +#define GL_WRITE_ONLY 0x88B9 // for wrappers only +#endif + +#ifndef GL_READ_WRITE +#define GL_READ_WRITE 0x88BA // for wrappers only +#endif + +#ifndef GL_LOWER_LEFT +#define GL_LOWER_LEFT 0x8CA1 // for wrappers only +#endif + +#ifndef GL_UPPER_LEFT +#define GL_UPPER_LEFT 0x8CA2 // for wrappers only +#endif + +#ifndef GL_TEXTURE_1D +#define GL_TEXTURE_1D 0x0DE0 // for wrappers only +#endif + +#ifndef GL_TEXTURE_1D_ARRAY +#define GL_TEXTURE_1D_ARRAY 0x8C18 // for wrappers only +#endif + +#ifndef GL_TEXTURE_RECTANGLE +#define GL_TEXTURE_RECTANGLE 0x84F5 // for wrappers only +#endif + +#ifndef GL_TEXTURE_CUBE_MAP_ARRAY +#define GL_TEXTURE_CUBE_MAP_ARRAY 0x9009 // GLES 3.2 +#endif + +#ifndef GL_TEXTURE_BUFFER +#define GL_TEXTURE_BUFFER 0x8C2A // GLES 3.2 +#endif + +#ifndef GL_TEXTURE_2D_MULTISAMPLE +#define GL_TEXTURE_2D_MULTISAMPLE 0x9100 // GLES 3.1 +#endif + +#ifndef GL_TEXTURE_2D_MULTISAMPLE_ARRAY +#define GL_TEXTURE_2D_MULTISAMPLE_ARRAY 0x9102 // GLES 3.1 +#endif + +#ifndef GL_PROXY_TEXTURE_3D +#define GL_PROXY_TEXTURE_3D 0x8070 // for wrappers only +#endif + +#ifndef GL_ATOMIC_COUNTER_BUFFER +#define GL_ATOMIC_COUNTER_BUFFER 0x92C0 // GLES 3.2 +#endif + +#ifndef GL_DISPATCH_INDIRECT_BUFFER +#define GL_DISPATCH_INDIRECT_BUFFER 0x90EE // GLES 3.2 +#endif + +#ifndef GL_DRAW_INDIRECT_BUFFER +#define GL_DRAW_INDIRECT_BUFFER 0x8F3F // GLES 3.2 +#endif + +#ifndef GL_QUERY_BUFFER +#define GL_QUERY_BUFFER 0x9192 // for wrappers only +#endif + +#ifndef GL_SHADER_STORAGE_BUFFER +#define GL_SHADER_STORAGE_BUFFER 0x90D2 // GLES 3.2 +#endif + +#ifndef GL_STENCIL_INDEX +#define GL_STENCIL_INDEX GL_STENCIL_INDEX8 // indirection +#endif + +#ifndef GL_INTERNALFORMAT_SUPPORTED +#define GL_INTERNALFORMAT_SUPPORTED 0x826F // for wrappers only +#endif + + + +// Used for glDepthRangef +typedef GLfloat GLclamp_t; + + +} // /namespace LLGL + + +#endif + + + +// ================================================================================ diff --git a/sources/Renderer/StaticModuleInterface.cpp b/sources/Renderer/StaticModuleInterface.cpp index 33838f6108..ce5e46296a 100644 --- a/sources/Renderer/StaticModuleInterface.cpp +++ b/sources/Renderer/StaticModuleInterface.cpp @@ -39,6 +39,10 @@ LLGL_DECLARE_STATIC_MODULE_INTERFACE(OpenGL); LLGL_DECLARE_STATIC_MODULE_INTERFACE(OpenGLES3); #endif +#ifdef LLGL_BUILD_RENDERER_WEBGL +LLGL_DECLARE_STATIC_MODULE_INTERFACE(WebGL); +#endif + #ifdef LLGL_BUILD_RENDERER_VULKAN LLGL_DECLARE_STATIC_MODULE_INTERFACE(Vulkan); #endif @@ -73,6 +77,9 @@ std::vector GetStaticModules() #ifdef LLGL_BUILD_RENDERER_OPENGLES3 ModuleOpenGLES3::GetModuleName(), #endif + #ifdef LLGL_BUILD_RENDERER_WEBGL + ModuleWebGL::GetModuleName(), + #endif #ifdef LLGL_BUILD_RENDERER_VULKAN ModuleVulkan::GetModuleName(), #endif @@ -106,6 +113,10 @@ const char* GetRendererName(const std::string& moduleName) LLGL_GET_RENDERER_NAME(ModuleOpenGLES3); #endif + #ifdef LLGL_BUILD_RENDERER_WEBGL + LLGL_GET_RENDERER_NAME(ModuleWebGL); + #endif + #ifdef LLGL_BUILD_RENDERER_VULKAN LLGL_GET_RENDERER_NAME(ModuleVulkan); #endif @@ -145,6 +156,10 @@ int GetRendererID(const std::string& moduleName) LLGL_GET_RENDERER_ID(ModuleOpenGLES3); #endif + #ifdef LLGL_BUILD_RENDERER_WEBGL + LLGL_GET_RENDERER_ID(ModuleWebGL); + #endif + #ifdef LLGL_BUILD_RENDERER_VULKAN LLGL_GET_RENDERER_ID(ModuleVulkan); #endif @@ -184,6 +199,10 @@ RenderSystem* AllocRenderSystem(const RenderSystemDescriptor& renderSystemDesc) LLGL_ALLOC_RENDER_SYSTEM(ModuleOpenGLES3); #endif + #ifdef LLGL_BUILD_RENDERER_WEBGL + LLGL_ALLOC_RENDER_SYSTEM(ModuleWebGL); + #endif + #ifdef LLGL_BUILD_RENDERER_VULKAN LLGL_ALLOC_RENDER_SYSTEM(ModuleVulkan); #endif From c6c9c6e0358bc508ca26c6434700e5589e0662a3 Mon Sep 17 00:00:00 2001 From: Laura Hermanns Date: Tue, 13 Aug 2024 23:46:22 -0400 Subject: [PATCH 06/15] [Wasm] Defined Wasm platform as mobile platform. - Moved EmscriptenWindow class to EmscriptenCanvas since browser canvas cannot be controlled like a desktop window. - Started with renaming Emscripten platform to Wasm platform. --- CMakeLists.txt | 6 +- ...criptenWindow.cpp => EmscriptenCanvas.cpp} | 284 +++++------------- .../Platform/Emscripten/EmscriptenCanvas.h | 74 +++++ .../Platform/Emscripten/EmscriptenDisplay.cpp | 55 +--- .../Platform/Emscripten/EmscriptenDisplay.h | 9 +- .../Platform/Emscripten/EmscriptenPath.cpp | 2 +- .../Platform/Emscripten/EmscriptenWindow.h | 89 ------ 7 files changed, 155 insertions(+), 364 deletions(-) rename sources/Platform/Emscripten/{EmscriptenWindow.cpp => EmscriptenCanvas.cpp} (56%) create mode 100644 sources/Platform/Emscripten/EmscriptenCanvas.h delete mode 100644 sources/Platform/Emscripten/EmscriptenWindow.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f2f5ab9f66..03ce10f9f0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,7 @@ endif() set(LLGL_UWP_PLATFORM OFF) set(LLGL_IOS_PLATFORM OFF) set(LLGL_ANDROID_PLATFORM OFF) +set(LLGL_HTML5_PLATFORM OFF) if(NOT DEFINED LLGL_TARGET_PLATFORM) if("${CMAKE_SYSTEM_NAME}" STREQUAL "Android") @@ -34,7 +35,8 @@ if(NOT DEFINED LLGL_TARGET_PLATFORM) set(LLGL_TARGET_PLATFORM "${CMAKE_SYSTEM_NAME} ${CMAKE_SYSTEM_VERSION}") set(LLGL_UWP_PLATFORM ON) elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Emscripten") - set(LLGL_TARGET_PLATFORM "Emscripten") + set(LLGL_TARGET_PLATFORM "HTML5") + set(LLGL_HTML5_PLATFORM ON) elseif(WIN32) if(LLGL_BUILD_64BIT) set(LLGL_TARGET_PLATFORM "Win64") @@ -93,7 +95,7 @@ set( BACKEND_INCLUDE_DIR "${PROJECT_INCLUDE_DIR}/LLGL/Backend" ) # === Macros === -if(LLGL_IOS_PLATFORM OR LLGL_ANDROID_PLATFORM) +if(LLGL_IOS_PLATFORM OR LLGL_ANDROID_PLATFORM OR LLGL_HTML5_PLATFORM) set(LLGL_MOBILE_PLATFORM ON) else() set(LLGL_MOBILE_PLATFORM OFF) diff --git a/sources/Platform/Emscripten/EmscriptenWindow.cpp b/sources/Platform/Emscripten/EmscriptenCanvas.cpp similarity index 56% rename from sources/Platform/Emscripten/EmscriptenWindow.cpp rename to sources/Platform/Emscripten/EmscriptenCanvas.cpp index ea0ed71efe..d5ebd9dcb2 100644 --- a/sources/Platform/Emscripten/EmscriptenWindow.cpp +++ b/sources/Platform/Emscripten/EmscriptenCanvas.cpp @@ -1,17 +1,17 @@ /* - * EmscriptenWindow.cpp + * EmscriptenCanvas.cpp * * Copyright (c) 2015 Lukas Hermanns. All rights reserved. * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). */ +#include "EmscriptenCanvas.h" +#include "MapKey.h" #include #include -#include "EmscriptenWindow.h" -#include "MapKey.h" #include "../../Core/CoreUtils.h" #include -#include + namespace LLGL { @@ -28,7 +28,7 @@ bool Surface::ProcessEvents() /* - * Window class + * Canvas class */ static Offset2D GetScreenCenteredPosition(const Extent2D& size) @@ -45,28 +45,22 @@ static Offset2D GetScreenCenteredPosition(const Extent2D& size) return {}; } -std::unique_ptr Window::Create(const WindowDescriptor& desc) +std::unique_ptr Canvas::Create(const CanvasDescriptor& desc) { - return MakeUnique(desc); + return MakeUnique(desc); } /* - * EmscriptenWindow class + * EmscriptenCanvas class */ -EmscriptenWindow::EmscriptenWindow(const WindowDescriptor& desc) : - desc_ { desc } -{ - CreateEmscriptenWindow(); -} - -EmscriptenWindow::~EmscriptenWindow() +EmscriptenCanvas::EmscriptenCanvas(const CanvasDescriptor& desc) { - + CreateEmscriptenCanvas(desc); } -bool EmscriptenWindow::GetNativeHandle(void* nativeHandle, std::size_t nativeHandleSize) +bool EmscriptenCanvas::GetNativeHandle(void* nativeHandle, std::size_t nativeHandleSize) { if (nativeHandle != nullptr && nativeHandleSize == sizeof(NativeHandle)) { @@ -77,138 +71,27 @@ bool EmscriptenWindow::GetNativeHandle(void* nativeHandle, std::size_t nativeHan return false; } -void EmscriptenWindow::ResetPixelFormat() -{ - // dummy -} - -Extent2D EmscriptenWindow::GetContentSize() const -{ - /* Return the size of the client area */ - return GetSize(true); -} - -void EmscriptenWindow::SetPosition(const Offset2D& position) -{ - /* Move window and store new position */ - //XMoveWindow(display_, wnd_, position.x, position.y); - //desc_.position = position; -} - -Offset2D EmscriptenWindow::GetPosition() const -{ - //XWindowAttributes attribs; - //XGetWindowAttributes(display_, wnd_, &attribs); - //return { attribs.x, attribs.y }; - - return {0, 0}; -} - -void EmscriptenWindow::SetSize(const Extent2D& size, bool useClientArea) -{ - //XResizeWindow(display_, wnd_, size.width, size.height); -} - -Extent2D EmscriptenWindow::GetSize(bool useClientArea) const +Extent2D EmscriptenCanvas::GetContentSize() const { - /* - XWindowAttributes attribs; - XGetWindowAttributes(display_, wnd_, &attribs); - */ - + int width = 0, height = 0; + emscripten_get_canvas_element_size("#canvas", &width, &height); return Extent2D { - static_cast(0), - static_cast(0) + static_cast(width), + static_cast(height) }; } -void EmscriptenWindow::SetTitle(const UTF8String& title) +void EmscriptenCanvas::SetTitle(const UTF8String& title) { - //XStoreName(display_, wnd_, title.c_str()); } -UTF8String EmscriptenWindow::GetTitle() const +UTF8String EmscriptenCanvas::GetTitle() const { char* title = nullptr; - //XFetchName(display_, wnd_, &title); return title; } -void EmscriptenWindow::Show(bool show) -{ - if (show) - { - /* Map window and reset window position */ - //XMapWindow(display_, wnd_); - //XMoveWindow(display_, wnd_, desc_.position.x, desc_.position.y); - } - else - { - //XUnmapWindow(display_, wnd_); - } - - if ((desc_.flags & WindowFlags::Borderless) != 0) - { - //XSetInputFocus(display_, (show ? wnd_ : None), RevertToParent, CurrentTime); - } -} - -bool EmscriptenWindow::IsShown() const -{ - return false; -} - -void EmscriptenWindow::SetDesc(const WindowDescriptor& desc) -{ - //todo... -} - -WindowDescriptor EmscriptenWindow::GetDesc() const -{ - return desc_; //todo... -} - -void EmscriptenWindow::ProcessEvent(/*XEvent& event*/) -{ - /* - switch (event.type) - { - case KeyPress: - ProcessKeyEvent(event.xkey, true); - break; - - case KeyRelease: - ProcessKeyEvent(event.xkey, false); - break; - - case ButtonPress: - ProcessMouseKeyEvent(event.xbutton, true); - break; - - case ButtonRelease: - ProcessMouseKeyEvent(event.xbutton, false); - break; - - case Expose: - ProcessExposeEvent(); - break; - - case MotionNotify: - ProcessMotionEvent(event.xmotion); - break; - - case DestroyNotify: - PostQuit(); - break; - - case ClientMessage: - ProcessClientMessage(event.xclient); - break; - } - */ -} - /* * ======= Private: ======= @@ -222,7 +105,7 @@ EM_JS(emscripten::EM_VAL, get_canvas, (), { canvas_ = emscripten::val::take_ownership(get_canvas()); */ -static inline const char *emscripten_event_type_to_string(int eventType) +static const char* emscripten_event_type_to_string(int eventType) { const char *events[] = { "(invalid)", "(none)", "keypress", "keydown", "keyup", "click", "mousedown", "mouseup", "dblclick", "mousemove", "wheel", "resize", "scroll", "blur", "focus", "focusin", "focusout", "deviceorientation", "devicemotion", "orientationchange", "fullscreenchange", "pointerlockchange", @@ -240,21 +123,24 @@ static inline const char *emscripten_event_type_to_string(int eventType) return events[eventType]; } -const char *emscripten_result_to_string(EMSCRIPTEN_RESULT result) +static const char* EmscriptenResultToString(EMSCRIPTEN_RESULT result) { - if (result == EMSCRIPTEN_RESULT_SUCCESS) return "EMSCRIPTEN_RESULT_SUCCESS"; - if (result == EMSCRIPTEN_RESULT_DEFERRED) return "EMSCRIPTEN_RESULT_DEFERRED"; - if (result == EMSCRIPTEN_RESULT_NOT_SUPPORTED) return "EMSCRIPTEN_RESULT_NOT_SUPPORTED"; - if (result == EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED) return "EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED"; - if (result == EMSCRIPTEN_RESULT_INVALID_TARGET) return "EMSCRIPTEN_RESULT_INVALID_TARGET"; - if (result == EMSCRIPTEN_RESULT_UNKNOWN_TARGET) return "EMSCRIPTEN_RESULT_UNKNOWN_TARGET"; - if (result == EMSCRIPTEN_RESULT_INVALID_PARAM) return "EMSCRIPTEN_RESULT_INVALID_PARAM"; - if (result == EMSCRIPTEN_RESULT_FAILED) return "EMSCRIPTEN_RESULT_FAILED"; - if (result == EMSCRIPTEN_RESULT_NO_DATA) return "EMSCRIPTEN_RESULT_NO_DATA"; + switch (result) + { + case EMSCRIPTEN_RESULT_SUCCESS: return "EMSCRIPTEN_RESULT_SUCCESS"; + case EMSCRIPTEN_RESULT_DEFERRED: return "EMSCRIPTEN_RESULT_DEFERRED"; + case EMSCRIPTEN_RESULT_NOT_SUPPORTED: return "EMSCRIPTEN_RESULT_NOT_SUPPORTED"; + case EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED: return "EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED"; + case EMSCRIPTEN_RESULT_INVALID_TARGET: return "EMSCRIPTEN_RESULT_INVALID_TARGET"; + case EMSCRIPTEN_RESULT_UNKNOWN_TARGET: return "EMSCRIPTEN_RESULT_UNKNOWN_TARGET"; + case EMSCRIPTEN_RESULT_INVALID_PARAM: return "EMSCRIPTEN_RESULT_INVALID_PARAM"; + case EMSCRIPTEN_RESULT_FAILED: return "EMSCRIPTEN_RESULT_FAILED"; + case EMSCRIPTEN_RESULT_NO_DATA: return "EMSCRIPTEN_RESULT_NO_DATA"; + } return "Unknown EMSCRIPTEN_RESULT!"; } -int interpret_charcode_for_keyevent(int eventType, const EmscriptenKeyboardEvent *e) +static int interpret_charcode_for_keyevent(int eventType, const EmscriptenKeyboardEvent *e) { // Only KeyPress events carry a charCode. For KeyDown and KeyUp events, these don't seem to be present yet, until later when the KeyDown // is transformed to KeyPress. Sometimes it can be useful to already at KeyDown time to know what the charCode of the resulting @@ -266,28 +152,28 @@ int interpret_charcode_for_keyevent(int eventType, const EmscriptenKeyboardEvent return e->keyCode; } -const char* EmscriptenWindow::OnBeforeUnloadCallback(int eventType, const void* reserved, void* userData) +const char* EmscriptenCanvas::OnBeforeUnloadCallback(int eventType, const void* reserved, void* userData) { - EmscriptenWindow *emscriptenWindow = ((EmscriptenWindow*)userData); - //cleanup the window - return NULL; + EmscriptenCanvas* canvas = reinterpret_cast(userData); + canvas->PostDestroy(); + return nullptr; } -int EmscriptenWindow::OnCanvasResizeCallback(int eventType, const EmscriptenUiEvent *keyEvent, void *userData) +int EmscriptenCanvas::OnCanvasResizeCallback(int eventType, const EmscriptenUiEvent* event, void *userData) { - EmscriptenWindow* window = (EmscriptenWindow*)userData; - - unsigned int width, height; - width = keyEvent->windowInnerWidth; - height = keyEvent->windowInnerHeight; - //EM_ASM({console.log("OnCanvasResizeCallback");}); - window->PostResize(Extent2D{ width, height }); + EmscriptenCanvas* canvas = reinterpret_cast(userData); + const Extent2D clientAreaSize + { + static_cast(event->documentBodyClientWidth), + static_cast(event->documentBodyClientHeight) + }; + canvas->PostResize(clientAreaSize); return 0; } -EM_BOOL EmscriptenWindow::OnKeyCallback(int eventType, const EmscriptenKeyboardEvent *e, void *userData) +EM_BOOL EmscriptenCanvas::OnKeyCallback(int eventType, const EmscriptenKeyboardEvent *e, void *userData) { - EmscriptenWindow* window = (EmscriptenWindow*)userData; + EmscriptenCanvas* canvas = reinterpret_cast(userData); int dom_pk_code = emscripten_compute_dom_pk_code(e->code); @@ -301,19 +187,18 @@ EM_BOOL EmscriptenWindow::OnKeyCallback(int eventType, const EmscriptenKeyboardE */ printf("%s, key: \"%s\", code: \"%s\" = %s (%d)\n", emscripten_event_type_to_string(eventType), e->key, e->code, emscripten_dom_pk_code_to_string(dom_pk_code), dom_pk_code); - EmscriptenWindow *emscriptenWindow = ((EmscriptenWindow*)userData); auto key = MapKey(e->code); if (eventType == 2) - window->PostKeyDown(key); + canvas->PostKeyDown(key); else if (eventType == 3) - window->PostKeyUp(key); + canvas->PostKeyUp(key); return true; } -EM_BOOL EmscriptenWindow::OnMouseCallback(int eventType, const EmscriptenMouseEvent *e, void *userData) +EM_BOOL EmscriptenCanvas::OnMouseCallback(int eventType, const EmscriptenMouseEvent *e, void *userData) { /* printf("%s, screen: (%ld,%ld), client: (%ld,%ld),%s%s%s%s button: %hu, buttons: %hu, movement: (%ld,%ld), canvas: (%ld,%ld), target: (%ld, %ld)\n", @@ -344,7 +229,7 @@ EM_BOOL EmscriptenWindow::OnMouseCallback(int eventType, const EmscriptenMouseEv return EMSCRIPTEN_RESULT_SUCCESS; } -EM_BOOL EmscriptenWindow::OnWheelCallback(int eventType, const EmscriptenWheelEvent *e, void *userData) +EM_BOOL EmscriptenCanvas::OnWheelCallback(int eventType, const EmscriptenWheelEvent *e, void *userData) { /* printf("%s, screen: (%ld,%ld), client: (%ld,%ld),%s%s%s%s button: %hu, buttons: %hu, canvas: (%ld,%ld), target: (%ld, %ld), delta:(%g,%g,%g), deltaMode:%lu\n", @@ -362,7 +247,7 @@ EM_BOOL EmscriptenWindow::OnWheelCallback(int eventType, const EmscriptenWheelEv return EMSCRIPTEN_RESULT_SUCCESS; } -void EmscriptenWindow::CreateEmscriptenWindow() +void EmscriptenCanvas::CreateEmscriptenCanvas(const CanvasDescriptor& desc) { /* Find canvas handle*/ emscripten::val config = emscripten::val::module_property("config"); @@ -374,13 +259,10 @@ void EmscriptenWindow::CreateEmscriptenWindow() if (!config.hasOwnProperty("canvas_selector")) return; - std::string canvas_selector = config["canvas_selector"].as(); canvas_ = document["body"].call("querySelector", canvas_selector); - //EM_ASM({console.log("isUndefined");}); - EM_ASM({ console.log(Emval.toValue($0)); }, canvas_.as_handle()); @@ -388,42 +270,26 @@ void EmscriptenWindow::CreateEmscriptenWindow() //emscripten::val console = emscripten::val::global("console"); //console.call("log", canvas); - /* Get final window position */ - if ((desc_.flags & WindowFlags::Centered) != 0) - desc_.position = GetScreenCenteredPosition(desc_.size); - - /* Set title and show window (if enabled) */ - SetTitle(desc_.title); - - /* Show window */ - if ((desc_.flags & WindowFlags::Visible) != 0) - { - - } - - /* Prepare borderless window */ - const bool isBorderless = ((desc_.flags & WindowFlags::Borderless) != 0); - if (isBorderless) - { - } + /* Set title and show canvas (if enabled) */ + SetTitle(desc.title); /* Set callbacks */ EMSCRIPTEN_RESULT ret; - ret = emscripten_set_beforeunload_callback((void*)this, EmscriptenWindow::OnBeforeUnloadCallback); - ret = emscripten_set_resize_callback(canvas_selector.c_str(), (void*)this, true, EmscriptenWindow::OnCanvasResizeCallback); - - ret = emscripten_set_keydown_callback(canvas_selector.c_str(), (void*)this, true, EmscriptenWindow::OnKeyCallback); - ret = emscripten_set_keyup_callback(canvas_selector.c_str(), (void*)this, true, EmscriptenWindow::OnKeyCallback); - - ret = emscripten_set_click_callback(canvas_selector.c_str(), (void*)this, true, EmscriptenWindow::OnMouseCallback); - ret = emscripten_set_mousedown_callback(canvas_selector.c_str(), (void*)this, true, EmscriptenWindow::OnMouseCallback); - ret = emscripten_set_mouseup_callback(canvas_selector.c_str(), (void*)this, true, EmscriptenWindow::OnMouseCallback); - ret = emscripten_set_dblclick_callback(canvas_selector.c_str(), (void*)this, true, EmscriptenWindow::OnMouseCallback); - ret = emscripten_set_mousemove_callback(canvas_selector.c_str(), (void*)this, true, EmscriptenWindow::OnMouseCallback); - ret = emscripten_set_wheel_callback(canvas_selector.c_str(), (void*)this, true, EmscriptenWindow::OnWheelCallback); + ret = emscripten_set_beforeunload_callback(this, EmscriptenCanvas::OnBeforeUnloadCallback); + ret = emscripten_set_resize_callback(canvas_selector.c_str(), this, true, EmscriptenCanvas::OnCanvasResizeCallback); + + ret = emscripten_set_keydown_callback(canvas_selector.c_str(), this, true, EmscriptenCanvas::OnKeyCallback); + ret = emscripten_set_keyup_callback(canvas_selector.c_str(), this, true, EmscriptenCanvas::OnKeyCallback); + + ret = emscripten_set_click_callback(canvas_selector.c_str(), this, true, EmscriptenCanvas::OnMouseCallback); + ret = emscripten_set_mousedown_callback(canvas_selector.c_str(), this, true, EmscriptenCanvas::OnMouseCallback); + ret = emscripten_set_mouseup_callback(canvas_selector.c_str(), this, true, EmscriptenCanvas::OnMouseCallback); + ret = emscripten_set_dblclick_callback(canvas_selector.c_str(), this, true, EmscriptenCanvas::OnMouseCallback); + ret = emscripten_set_mousemove_callback(canvas_selector.c_str(), this, true, EmscriptenCanvas::OnMouseCallback); + ret = emscripten_set_wheel_callback(canvas_selector.c_str(), this, true, EmscriptenCanvas::OnWheelCallback); } -void EmscriptenWindow::ProcessKeyEvent(/*XKeyEvent& event, bool down*/) +void EmscriptenCanvas::ProcessKeyEvent(/*event, bool down*/) { /*auto key = MapKey(event); if (down) @@ -432,7 +298,7 @@ void EmscriptenWindow::ProcessKeyEvent(/*XKeyEvent& event, bool down*/) PostKeyUp(key);*/ } -void EmscriptenWindow::ProcessMouseKeyEvent(/*XButtonEvent& event, bool down*/) +void EmscriptenCanvas::ProcessMouseKeyEvent(/*event, bool down*/) { /*switch (event.button) { @@ -454,28 +320,24 @@ void EmscriptenWindow::ProcessMouseKeyEvent(/*XButtonEvent& event, bool down*/) }*/ } -void EmscriptenWindow::ProcessExposeEvent() +/*void EmscriptenCanvas::ProcessExposeEvent() { - //XWindowAttributes attribs; - //XGetWindowAttributes(display_, wnd_, &attribs); - const Extent2D size { static_cast(0), static_cast(0) }; - PostResize(size); -} +}*/ -void EmscriptenWindow::ProcessClientMessage(/*XClientMessageEvent& event*/) +void EmscriptenCanvas::ProcessClientMessage(/*XClientMessageEvent& event*/) { /*Atom atom = static_cast(event.data.l[0]); if (atom == closeWndAtom_) PostQuit();*/ } -void EmscriptenWindow::ProcessMotionEvent(/*XMotionEvent& event*/) +void EmscriptenCanvas::ProcessMotionEvent(/*XMotionEvent& event*/) { /*const Offset2D mousePos { event.x, event.y }; PostLocalMotion(mousePos); @@ -483,7 +345,7 @@ void EmscriptenWindow::ProcessMotionEvent(/*XMotionEvent& event*/) prevMousePos_ = mousePos;*/ } -void EmscriptenWindow::PostMouseKeyEvent(Key key, bool down) +void EmscriptenCanvas::PostMouseKeyEvent(Key key, bool down) { if (down) PostKeyDown(key); diff --git a/sources/Platform/Emscripten/EmscriptenCanvas.h b/sources/Platform/Emscripten/EmscriptenCanvas.h new file mode 100644 index 0000000000..17102e8b40 --- /dev/null +++ b/sources/Platform/Emscripten/EmscriptenCanvas.h @@ -0,0 +1,74 @@ +/* + * EmscriptenCanvas.h + * + * Copyright (c) 2015 Lukas Hermanns. All rights reserved. + * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). + */ + +#ifndef LLGL_EMSCRIPTEN_CANVAS_H +#define LLGL_EMSCRIPTEN_CANVAS_H + + +#include +#include +#include +#include +#include +#include + + +namespace LLGL +{ + + +class EmscriptenCanvas : public Canvas +{ + + public: + + EmscriptenCanvas(const CanvasDescriptor& desc); + + bool GetNativeHandle(void* nativeHandle, std::size_t nativeHandleSize) override; + + Extent2D GetContentSize() const override; + + void SetTitle(const UTF8String& title) override; + UTF8String GetTitle() const override; + + private: + + void CreateEmscriptenCanvas(const CanvasDescriptor& desc); + + void ProcessKeyEvent(/*event, bool down*/); + void ProcessMouseKeyEvent(/*event, bool down*/); + //void ProcessExposeEvent(); + void ProcessClientMessage(/*event*/); + void ProcessMotionEvent(/*event*/); + + void PostMouseKeyEvent(Key key, bool down); + + private: + + static const char* OnBeforeUnloadCallback(int eventType, const void* reserved, void* userData); + static int OnCanvasResizeCallback(int eventType, const EmscriptenUiEvent *keyEvent, void *userData); + + static EM_BOOL OnKeyCallback(int eventType, const EmscriptenKeyboardEvent *e, void *userData); + + static EM_BOOL OnMouseCallback(int eventType, const EmscriptenMouseEvent *e, void *userData); + static EM_BOOL OnWheelCallback(int eventType, const EmscriptenWheelEvent *e, void *userData); + + private: + + emscripten::val canvas_; + +}; + + +} // /namespace LLGL + + +#endif + + + +// ================================================================================ diff --git a/sources/Platform/Emscripten/EmscriptenDisplay.cpp b/sources/Platform/Emscripten/EmscriptenDisplay.cpp index abb98d9c41..504804e8d3 100644 --- a/sources/Platform/Emscripten/EmscriptenDisplay.cpp +++ b/sources/Platform/Emscripten/EmscriptenDisplay.cpp @@ -24,44 +24,26 @@ std::size_t Display::Count() Display* const * Display::GetList() { - /* - if (UpdateDisplayList() || g_displayRefList.empty()) - { - /* Update reference list and append null terminator to array */ - /*g_displayRefList.clear(); - g_displayRefList.reserve(g_displayList.size() + 1); - for (const auto& display : g_displayList) - g_displayRefList.push_back(display.get()); - g_displayRefList.push_back(nullptr); - } - return g_displayRefList.data(); - */ - return nullptr; } Display* Display::Get(std::size_t index) { - //UpdateDisplayList(); - //return (index < g_displayList.size() ? g_displayList[index].get() : nullptr); return nullptr; } Display* Display::GetPrimary() { - //UpdateDisplayList(); return nullptr; } bool Display::ShowCursor(bool show) { - //TODO return false; } bool Display::IsCursorShown() { - //TODO return true; } @@ -94,16 +76,12 @@ bool EmscriptenDisplay::IsPrimary() const UTF8String EmscriptenDisplay::GetDeviceName() const { - return UTF8String{"device name"}; + return UTF8String{ "device name" }; } Offset2D EmscriptenDisplay::GetOffset() const { - /* Get display offset from position of root window */ - return Offset2D - { - 0, 0 - }; + return Offset2D{}; } float EmscriptenDisplay::GetScale() const @@ -113,34 +91,11 @@ float EmscriptenDisplay::GetScale() const bool EmscriptenDisplay::ResetDisplayMode() { - //TODO return false; } bool EmscriptenDisplay::SetDisplayMode(const DisplayMode& displayMode) { - /* Get all screen sizes from X11 extension Xrandr */ - int numSizes = 0; - - /* - XRRScreenSize* scrSizes = XRRSizes(GetNative(), screen_, &numSizes); - - for (int i = 0; i < numSizes; ++i) - { - // Check if specified display mode resolution matches this screen configuration - if (displayMode.resolution.width == static_cast(scrSizes[i].width) && - displayMode.resolution.height == static_cast(scrSizes[i].height)) - { - if (XRRScreenConfiguration* scrCfg = XRRGetScreenInfo(dpy, rootWnd)) - { - Status status = XRRSetScreenConfig(dpy, scrCfg, rootWnd, i, RR_Rotate_0, 0); - XRRFreeScreenConfigInfo(scrCfg); - return (status != 0); - } - } - } - */ - return false; } @@ -161,12 +116,6 @@ std::vector EmscriptenDisplay::GetSupportedDisplayModes() const } -/* - * ======= Private: ======= - */ - - - } // /namespace LLGL diff --git a/sources/Platform/Emscripten/EmscriptenDisplay.h b/sources/Platform/Emscripten/EmscriptenDisplay.h index 20041c4368..004e53842f 100644 --- a/sources/Platform/Emscripten/EmscriptenDisplay.h +++ b/sources/Platform/Emscripten/EmscriptenDisplay.h @@ -10,12 +10,7 @@ #include -#include -#include -#include -#include -#include namespace LLGL { @@ -43,9 +38,7 @@ class EmscriptenDisplay : public Display private: - private: - - int screen_ = 0; + int screen_ = 0; }; diff --git a/sources/Platform/Emscripten/EmscriptenPath.cpp b/sources/Platform/Emscripten/EmscriptenPath.cpp index d05f97dd5b..2c0709e541 100644 --- a/sources/Platform/Emscripten/EmscriptenPath.cpp +++ b/sources/Platform/Emscripten/EmscriptenPath.cpp @@ -27,7 +27,7 @@ LLGL_EXPORT UTF8String GetWorkingDir() return UTF8String{ ::getcwd(path, sizeof(path)) }; } -LLGL_EXPORT UTF8String GetAbsolutePath(const StringView& filename) +LLGL_EXPORT UTF8String GetAbsolutePath(const UTF8String& filename) { return Combine(GetWorkingDir(), filename); } diff --git a/sources/Platform/Emscripten/EmscriptenWindow.h b/sources/Platform/Emscripten/EmscriptenWindow.h deleted file mode 100644 index a6ddee521b..0000000000 --- a/sources/Platform/Emscripten/EmscriptenWindow.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - * LinuxWindow.h - * - * Copyright (c) 2015 Lukas Hermanns. All rights reserved. - * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). - */ - -#ifndef LLGL_EMSCRIPTEN_WINDOW_H -#define LLGL_EMSCRIPTEN_WINDOW_H - - -#include -#include "EmscriptenDisplay.h" - -namespace LLGL -{ - - -class EmscriptenWindow : public Window -{ - - public: - - EmscriptenWindow(const WindowDescriptor& desc); - ~EmscriptenWindow(); - - bool GetNativeHandle(void* nativeHandle, std::size_t nativeHandleSize) override; - - void ResetPixelFormat() override; - - Extent2D GetContentSize() const override; - - void SetPosition(const Offset2D& position) override; - Offset2D GetPosition() const override; - - void SetSize(const Extent2D& size, bool useClientArea = true) override; - Extent2D GetSize(bool useClientArea = true) const override; - - void SetTitle(const UTF8String& title) override; - UTF8String GetTitle() const override; - - void Show(bool show = true) override; - bool IsShown() const override; - - void SetDesc(const WindowDescriptor& desc) override; - WindowDescriptor GetDesc() const override; - - public: - - void ProcessEvent(/*XEvent& event*/); - - private: - - void CreateEmscriptenWindow(); - - void ProcessKeyEvent(/*XKeyEvent& event, bool down*/); - void ProcessMouseKeyEvent(/*XButtonEvent& event, bool down*/); - void ProcessExposeEvent(); - void ProcessClientMessage(/*XClientMessageEvent& event*/); - void ProcessMotionEvent(/*XMotionEvent& event*/); - - void PostMouseKeyEvent(Key key, bool down); - - private: - static const char* OnBeforeUnloadCallback(int eventType, const void* reserved, void* userData); - static int OnCanvasResizeCallback(int eventType, const EmscriptenUiEvent *keyEvent, void *userData); - - static EM_BOOL OnKeyCallback(int eventType, const EmscriptenKeyboardEvent *e, void *userData); - - static EM_BOOL OnMouseCallback(int eventType, const EmscriptenMouseEvent *e, void *userData); - static EM_BOOL OnWheelCallback(int eventType, const EmscriptenWheelEvent *e, void *userData); - - private: - - WindowDescriptor desc_; - emscripten::val canvas_; - Offset2D prevMousePos_; - -}; - - -} // /namespace LLGL - - -#endif - - - -// ================================================================================ From 150f71f18b9bad2293a2db76809c8f21a5e6be8f Mon Sep 17 00:00:00 2001 From: Laura Hermanns Date: Wed, 21 Aug 2024 21:57:18 -0400 Subject: [PATCH 07/15] [Wasm] Renamed Emscripten files to Wasm. Emscripten is the toolchain, not the platform. --- .../EmscriptenNativeHandle.h => Wasm/WasmNativeHandle.h} | 0 .../EmscriptenNativeHandle.h => Wasm/WasmNativeHandle.h} | 0 .../{Emscripten/EmscriptenCanvas.cpp => Wasm/WasmCanvas.cpp} | 0 .../Platform/{Emscripten/EmscriptenCanvas.h => Wasm/WasmCanvas.h} | 0 .../{Emscripten/EmscriptenDebug.cpp => Wasm/WasmDebug.cpp} | 0 .../Platform/{Emscripten/EmscriptenDebug.h => Wasm/WasmDebug.h} | 0 .../{Emscripten/EmscriptenDisplay.cpp => Wasm/WasmDisplay.cpp} | 0 .../{Emscripten/EmscriptenDisplay.h => Wasm/WasmDisplay.h} | 0 sources/Platform/{Emscripten/MapKey.cpp => Wasm/WasmKeyCodes.cpp} | 0 sources/Platform/{Emscripten/MapKey.h => Wasm/WasmKeyCodes.h} | 0 .../{Emscripten/EmscriptenModule.cpp => Wasm/WasmModule.cpp} | 0 .../Platform/{Emscripten/EmscriptenModule.h => Wasm/WasmModule.h} | 0 .../Platform/{Emscripten/EmscriptenPath.cpp => Wasm/WasmPath.cpp} | 0 .../{Emscripten/EmscriptenTimer.cpp => Wasm/WasmTimer.cpp} | 0 .../EmscriptenGLContext.cpp => Wasm/WasmGLContext.cpp} | 0 .../{Emscripten/EmscriptenGLContext.h => Wasm/WasmGLContext.h} | 0 .../WasmGLSwapChainContext.cpp} | 0 .../WasmGLSwapChainContext.h} | 0 18 files changed, 0 insertions(+), 0 deletions(-) rename include/LLGL/Backend/OpenGL/{Emscripten/EmscriptenNativeHandle.h => Wasm/WasmNativeHandle.h} (100%) rename include/LLGL/Platform/{Emscripten/EmscriptenNativeHandle.h => Wasm/WasmNativeHandle.h} (100%) rename sources/Platform/{Emscripten/EmscriptenCanvas.cpp => Wasm/WasmCanvas.cpp} (100%) rename sources/Platform/{Emscripten/EmscriptenCanvas.h => Wasm/WasmCanvas.h} (100%) rename sources/Platform/{Emscripten/EmscriptenDebug.cpp => Wasm/WasmDebug.cpp} (100%) rename sources/Platform/{Emscripten/EmscriptenDebug.h => Wasm/WasmDebug.h} (100%) rename sources/Platform/{Emscripten/EmscriptenDisplay.cpp => Wasm/WasmDisplay.cpp} (100%) rename sources/Platform/{Emscripten/EmscriptenDisplay.h => Wasm/WasmDisplay.h} (100%) rename sources/Platform/{Emscripten/MapKey.cpp => Wasm/WasmKeyCodes.cpp} (100%) rename sources/Platform/{Emscripten/MapKey.h => Wasm/WasmKeyCodes.h} (100%) rename sources/Platform/{Emscripten/EmscriptenModule.cpp => Wasm/WasmModule.cpp} (100%) rename sources/Platform/{Emscripten/EmscriptenModule.h => Wasm/WasmModule.h} (100%) rename sources/Platform/{Emscripten/EmscriptenPath.cpp => Wasm/WasmPath.cpp} (100%) rename sources/Platform/{Emscripten/EmscriptenTimer.cpp => Wasm/WasmTimer.cpp} (100%) rename sources/Renderer/OpenGL/Platform/{Emscripten/EmscriptenGLContext.cpp => Wasm/WasmGLContext.cpp} (100%) rename sources/Renderer/OpenGL/Platform/{Emscripten/EmscriptenGLContext.h => Wasm/WasmGLContext.h} (100%) rename sources/Renderer/OpenGL/Platform/{Emscripten/EmscriptenGLSwapChainContext.cpp => Wasm/WasmGLSwapChainContext.cpp} (100%) rename sources/Renderer/OpenGL/Platform/{Emscripten/EmscriptenGLSwapChainContext.h => Wasm/WasmGLSwapChainContext.h} (100%) diff --git a/include/LLGL/Backend/OpenGL/Emscripten/EmscriptenNativeHandle.h b/include/LLGL/Backend/OpenGL/Wasm/WasmNativeHandle.h similarity index 100% rename from include/LLGL/Backend/OpenGL/Emscripten/EmscriptenNativeHandle.h rename to include/LLGL/Backend/OpenGL/Wasm/WasmNativeHandle.h diff --git a/include/LLGL/Platform/Emscripten/EmscriptenNativeHandle.h b/include/LLGL/Platform/Wasm/WasmNativeHandle.h similarity index 100% rename from include/LLGL/Platform/Emscripten/EmscriptenNativeHandle.h rename to include/LLGL/Platform/Wasm/WasmNativeHandle.h diff --git a/sources/Platform/Emscripten/EmscriptenCanvas.cpp b/sources/Platform/Wasm/WasmCanvas.cpp similarity index 100% rename from sources/Platform/Emscripten/EmscriptenCanvas.cpp rename to sources/Platform/Wasm/WasmCanvas.cpp diff --git a/sources/Platform/Emscripten/EmscriptenCanvas.h b/sources/Platform/Wasm/WasmCanvas.h similarity index 100% rename from sources/Platform/Emscripten/EmscriptenCanvas.h rename to sources/Platform/Wasm/WasmCanvas.h diff --git a/sources/Platform/Emscripten/EmscriptenDebug.cpp b/sources/Platform/Wasm/WasmDebug.cpp similarity index 100% rename from sources/Platform/Emscripten/EmscriptenDebug.cpp rename to sources/Platform/Wasm/WasmDebug.cpp diff --git a/sources/Platform/Emscripten/EmscriptenDebug.h b/sources/Platform/Wasm/WasmDebug.h similarity index 100% rename from sources/Platform/Emscripten/EmscriptenDebug.h rename to sources/Platform/Wasm/WasmDebug.h diff --git a/sources/Platform/Emscripten/EmscriptenDisplay.cpp b/sources/Platform/Wasm/WasmDisplay.cpp similarity index 100% rename from sources/Platform/Emscripten/EmscriptenDisplay.cpp rename to sources/Platform/Wasm/WasmDisplay.cpp diff --git a/sources/Platform/Emscripten/EmscriptenDisplay.h b/sources/Platform/Wasm/WasmDisplay.h similarity index 100% rename from sources/Platform/Emscripten/EmscriptenDisplay.h rename to sources/Platform/Wasm/WasmDisplay.h diff --git a/sources/Platform/Emscripten/MapKey.cpp b/sources/Platform/Wasm/WasmKeyCodes.cpp similarity index 100% rename from sources/Platform/Emscripten/MapKey.cpp rename to sources/Platform/Wasm/WasmKeyCodes.cpp diff --git a/sources/Platform/Emscripten/MapKey.h b/sources/Platform/Wasm/WasmKeyCodes.h similarity index 100% rename from sources/Platform/Emscripten/MapKey.h rename to sources/Platform/Wasm/WasmKeyCodes.h diff --git a/sources/Platform/Emscripten/EmscriptenModule.cpp b/sources/Platform/Wasm/WasmModule.cpp similarity index 100% rename from sources/Platform/Emscripten/EmscriptenModule.cpp rename to sources/Platform/Wasm/WasmModule.cpp diff --git a/sources/Platform/Emscripten/EmscriptenModule.h b/sources/Platform/Wasm/WasmModule.h similarity index 100% rename from sources/Platform/Emscripten/EmscriptenModule.h rename to sources/Platform/Wasm/WasmModule.h diff --git a/sources/Platform/Emscripten/EmscriptenPath.cpp b/sources/Platform/Wasm/WasmPath.cpp similarity index 100% rename from sources/Platform/Emscripten/EmscriptenPath.cpp rename to sources/Platform/Wasm/WasmPath.cpp diff --git a/sources/Platform/Emscripten/EmscriptenTimer.cpp b/sources/Platform/Wasm/WasmTimer.cpp similarity index 100% rename from sources/Platform/Emscripten/EmscriptenTimer.cpp rename to sources/Platform/Wasm/WasmTimer.cpp diff --git a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.cpp b/sources/Renderer/OpenGL/Platform/Wasm/WasmGLContext.cpp similarity index 100% rename from sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.cpp rename to sources/Renderer/OpenGL/Platform/Wasm/WasmGLContext.cpp diff --git a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.h b/sources/Renderer/OpenGL/Platform/Wasm/WasmGLContext.h similarity index 100% rename from sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLContext.h rename to sources/Renderer/OpenGL/Platform/Wasm/WasmGLContext.h diff --git a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.cpp b/sources/Renderer/OpenGL/Platform/Wasm/WasmGLSwapChainContext.cpp similarity index 100% rename from sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.cpp rename to sources/Renderer/OpenGL/Platform/Wasm/WasmGLSwapChainContext.cpp diff --git a/sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.h b/sources/Renderer/OpenGL/Platform/Wasm/WasmGLSwapChainContext.h similarity index 100% rename from sources/Renderer/OpenGL/Platform/Emscripten/EmscriptenGLSwapChainContext.h rename to sources/Renderer/OpenGL/Platform/Wasm/WasmGLSwapChainContext.h From 7ff836720c6672371dd293dc320cd541aa9c3747 Mon Sep 17 00:00:00 2001 From: Laura Hermanns Date: Wed, 21 Aug 2024 22:27:12 -0400 Subject: [PATCH 08/15] [Wasm] Renamed all Emscripten class names to Wasm. Appendix to renaming of filenames. --- CMakeLists.txt | 12 +-- include/LLGL/Backend/OpenGL/NativeHandle.h | 4 +- .../Backend/OpenGL/Wasm/WasmNativeHandle.h | 6 +- include/LLGL/Platform/NativeHandle.h | 4 +- include/LLGL/Platform/Platform.h | 2 +- include/LLGL/Platform/Wasm/WasmNativeHandle.h | 10 ++- sources/Platform/Debug.h | 4 +- sources/Platform/Wasm/WasmCanvas.cpp | 79 ++++++++++--------- sources/Platform/Wasm/WasmCanvas.h | 10 +-- sources/Platform/Wasm/WasmDebug.cpp | 2 +- sources/Platform/Wasm/WasmDebug.h | 6 +- sources/Platform/Wasm/WasmDisplay.cpp | 31 +++----- sources/Platform/Wasm/WasmDisplay.h | 14 ++-- sources/Platform/Wasm/WasmKeyCodes.cpp | 6 +- sources/Platform/Wasm/WasmKeyCodes.h | 8 +- sources/Platform/Wasm/WasmModule.cpp | 12 +-- sources/Platform/Wasm/WasmModule.h | 12 +-- sources/Platform/Wasm/WasmPath.cpp | 2 +- sources/Platform/Wasm/WasmTimer.cpp | 2 +- sources/Renderer/OpenGL/CMakeLists.txt | 4 +- .../OpenGL/Platform/Wasm/WasmGLContext.cpp | 26 +++--- .../OpenGL/Platform/Wasm/WasmGLContext.h | 16 ++-- .../Platform/Wasm/WasmGLSwapChainContext.cpp | 22 +++--- .../Platform/Wasm/WasmGLSwapChainContext.h | 18 ++--- sources/Renderer/OpenGL/WebGLProfile/WebGL.h | 2 +- 25 files changed, 153 insertions(+), 161 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 03ce10f9f0..a42f465215 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,7 @@ endif() set(LLGL_UWP_PLATFORM OFF) set(LLGL_IOS_PLATFORM OFF) set(LLGL_ANDROID_PLATFORM OFF) -set(LLGL_HTML5_PLATFORM OFF) +set(LLGL_WASM_PLATFORM OFF) if(NOT DEFINED LLGL_TARGET_PLATFORM) if("${CMAKE_SYSTEM_NAME}" STREQUAL "Android") @@ -35,8 +35,8 @@ if(NOT DEFINED LLGL_TARGET_PLATFORM) set(LLGL_TARGET_PLATFORM "${CMAKE_SYSTEM_NAME} ${CMAKE_SYSTEM_VERSION}") set(LLGL_UWP_PLATFORM ON) elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Emscripten") - set(LLGL_TARGET_PLATFORM "HTML5") - set(LLGL_HTML5_PLATFORM ON) + set(LLGL_TARGET_PLATFORM "WebAssembly") + set(LLGL_WASM_PLATFORM ON) elseif(WIN32) if(LLGL_BUILD_64BIT) set(LLGL_TARGET_PLATFORM "Win64") @@ -95,7 +95,7 @@ set( BACKEND_INCLUDE_DIR "${PROJECT_INCLUDE_DIR}/LLGL/Backend" ) # === Macros === -if(LLGL_IOS_PLATFORM OR LLGL_ANDROID_PLATFORM OR LLGL_HTML5_PLATFORM) +if(LLGL_IOS_PLATFORM OR LLGL_ANDROID_PLATFORM OR LLGL_WASM_PLATFORM) set(LLGL_MOBILE_PLATFORM ON) else() set(LLGL_MOBILE_PLATFORM OFF) @@ -538,8 +538,8 @@ if(LLGL_ENABLE_DEBUG_LAYER) endif() if(EMSCRIPTEN) - find_source_files(FilesPlatform CXX "${PROJECT_SOURCE_DIR}/sources/Platform/Emscripten") - find_source_files(FilesIncludePlatform CXX "${PROJECT_INCLUDE_DIR}/LLGL/Platform/Emscripten") + find_source_files(FilesPlatform CXX "${PROJECT_SOURCE_DIR}/sources/Platform/Wasm") + find_source_files(FilesIncludePlatform CXX "${PROJECT_INCLUDE_DIR}/LLGL/Platform/Wasm") elseif(WIN32) if(LLGL_UWP_PLATFORM) find_source_files(FilesPlatform CXX "${PROJECT_SOURCE_DIR}/sources/Platform/UWP") diff --git a/include/LLGL/Backend/OpenGL/NativeHandle.h b/include/LLGL/Backend/OpenGL/NativeHandle.h index 8892113139..8934f41273 100644 --- a/include/LLGL/Backend/OpenGL/NativeHandle.h +++ b/include/LLGL/Backend/OpenGL/NativeHandle.h @@ -21,8 +21,8 @@ # include #elif defined(LLGL_OS_ANDROID) # include -#elif defined(LLGL_OS_EMSCRIPTEN) -# include +#elif defined(LLGL_OS_WASM) +# include #endif diff --git a/include/LLGL/Backend/OpenGL/Wasm/WasmNativeHandle.h b/include/LLGL/Backend/OpenGL/Wasm/WasmNativeHandle.h index 01c52f730b..a706e4510c 100644 --- a/include/LLGL/Backend/OpenGL/Wasm/WasmNativeHandle.h +++ b/include/LLGL/Backend/OpenGL/Wasm/WasmNativeHandle.h @@ -1,12 +1,12 @@ /* - * EmscriptenNativeHandle.h (OpenGL) + * WasmNativeHandle.h (OpenGL) * * Copyright (c) 2015 Lukas Hermanns. All rights reserved. * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). */ -#ifndef LLGL_OPENGL_EMSCRIPTEN_NATIVE_HANDLE_H -#define LLGL_OPENGL_EMSCRIPTEN_NATIVE_HANDLE_H +#ifndef LLGL_OPENGL_WASM_NATIVE_HANDLE_H +#define LLGL_OPENGL_WASM_NATIVE_HANDLE_H #include diff --git a/include/LLGL/Platform/NativeHandle.h b/include/LLGL/Platform/NativeHandle.h index c0099f96eb..d4a35a42c7 100644 --- a/include/LLGL/Platform/NativeHandle.h +++ b/include/LLGL/Platform/NativeHandle.h @@ -19,8 +19,8 @@ # include #elif defined(LLGL_OS_LINUX) # include -#elif defined(LLGL_OS_EMSCRIPTEN) -# include +#elif defined(LLGL_OS_WASM) +# include #elif defined(LLGL_OS_IOS) # include #elif defined(LLGL_OS_ANDROID) diff --git a/include/LLGL/Platform/Platform.h b/include/LLGL/Platform/Platform.h index 45313fd5b2..372ddea99b 100644 --- a/include/LLGL/Platform/Platform.h +++ b/include/LLGL/Platform/Platform.h @@ -31,7 +31,7 @@ see https://sourceforge.net/p/predef/wiki/OperatingSystems/ #elif defined __ANDROID__ || defined ANDROID # define LLGL_OS_ANDROID #elif defined EMSCRIPTEN -# define LLGL_OS_EMSCRIPTEN +# define LLGL_OS_WASM #elif defined __linux__ # define LLGL_OS_LINUX #endif diff --git a/include/LLGL/Platform/Wasm/WasmNativeHandle.h b/include/LLGL/Platform/Wasm/WasmNativeHandle.h index cf8f55d878..e09d5f3bd9 100644 --- a/include/LLGL/Platform/Wasm/WasmNativeHandle.h +++ b/include/LLGL/Platform/Wasm/WasmNativeHandle.h @@ -1,15 +1,17 @@ /* - * LinuxNativeHandle.h + * WasmNativeHandle.h * * Copyright (c) 2015 Lukas Hermanns. All rights reserved. * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). */ -#ifndef LLGL_EMSCRIPTEN_NATIVE_HANDLE_H -#define LLGL_EMSCRIPTEN_NATIVE_HANDLE_H +#ifndef LLGL_WASM_NATIVE_HANDLE_H +#define LLGL_WASM_NATIVE_HANDLE_H + #include + namespace LLGL { @@ -18,7 +20,7 @@ namespace LLGL struct NativeHandle { //! CSS selector of canvas object. - std::string canvas; + std::string canvas; }; diff --git a/sources/Platform/Debug.h b/sources/Platform/Debug.h index e6be12d3ba..2eface2e14 100644 --- a/sources/Platform/Debug.h +++ b/sources/Platform/Debug.h @@ -22,8 +22,8 @@ # include "MacOS/MacOSDebug.h" # elif defined(LLGL_OS_LINUX) # include "Linux/LinuxDebug.h" -# elif defined(LLGL_OS_EMSCRIPTEN) -# include "Emscripten/EmscriptenDebug.h" +# elif defined(LLGL_OS_WASM) +# include "Wasm/WasmDebug.h" # elif defined(LLGL_OS_IOS) # include "IOS/IOSDebug.h" # elif defined(LLGL_OS_ANDROID) diff --git a/sources/Platform/Wasm/WasmCanvas.cpp b/sources/Platform/Wasm/WasmCanvas.cpp index d5ebd9dcb2..002d0de005 100644 --- a/sources/Platform/Wasm/WasmCanvas.cpp +++ b/sources/Platform/Wasm/WasmCanvas.cpp @@ -1,12 +1,12 @@ /* - * EmscriptenCanvas.cpp + * WasmCanvas.cpp * * Copyright (c) 2015 Lukas Hermanns. All rights reserved. * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). */ -#include "EmscriptenCanvas.h" -#include "MapKey.h" +#include "WasmCanvas.h" +#include "WasmKeyCodes.h" #include #include #include "../../Core/CoreUtils.h" @@ -47,20 +47,20 @@ static Offset2D GetScreenCenteredPosition(const Extent2D& size) std::unique_ptr Canvas::Create(const CanvasDescriptor& desc) { - return MakeUnique(desc); + return MakeUnique(desc); } /* - * EmscriptenCanvas class + * WasmCanvas class */ -EmscriptenCanvas::EmscriptenCanvas(const CanvasDescriptor& desc) +WasmCanvas::WasmCanvas(const CanvasDescriptor& desc) { CreateEmscriptenCanvas(desc); } -bool EmscriptenCanvas::GetNativeHandle(void* nativeHandle, std::size_t nativeHandleSize) +bool WasmCanvas::GetNativeHandle(void* nativeHandle, std::size_t nativeHandleSize) { if (nativeHandle != nullptr && nativeHandleSize == sizeof(NativeHandle)) { @@ -71,7 +71,7 @@ bool EmscriptenCanvas::GetNativeHandle(void* nativeHandle, std::size_t nativeHan return false; } -Extent2D EmscriptenCanvas::GetContentSize() const +Extent2D WasmCanvas::GetContentSize() const { int width = 0, height = 0; emscripten_get_canvas_element_size("#canvas", &width, &height); @@ -82,13 +82,14 @@ Extent2D EmscriptenCanvas::GetContentSize() const }; } -void EmscriptenCanvas::SetTitle(const UTF8String& title) +void WasmCanvas::SetTitle(const UTF8String& title) { + //todo } -UTF8String EmscriptenCanvas::GetTitle() const +UTF8String WasmCanvas::GetTitle() const { - char* title = nullptr; + char* title = nullptr; //todo return title; } @@ -152,16 +153,16 @@ static int interpret_charcode_for_keyevent(int eventType, const EmscriptenKeyboa return e->keyCode; } -const char* EmscriptenCanvas::OnBeforeUnloadCallback(int eventType, const void* reserved, void* userData) +const char* WasmCanvas::OnBeforeUnloadCallback(int eventType, const void* reserved, void* userData) { - EmscriptenCanvas* canvas = reinterpret_cast(userData); + WasmCanvas* canvas = reinterpret_cast(userData); canvas->PostDestroy(); return nullptr; } -int EmscriptenCanvas::OnCanvasResizeCallback(int eventType, const EmscriptenUiEvent* event, void *userData) +int WasmCanvas::OnCanvasResizeCallback(int eventType, const EmscriptenUiEvent* event, void *userData) { - EmscriptenCanvas* canvas = reinterpret_cast(userData); + WasmCanvas* canvas = reinterpret_cast(userData); const Extent2D clientAreaSize { static_cast(event->documentBodyClientWidth), @@ -171,9 +172,9 @@ int EmscriptenCanvas::OnCanvasResizeCallback(int eventType, const EmscriptenUiEv return 0; } -EM_BOOL EmscriptenCanvas::OnKeyCallback(int eventType, const EmscriptenKeyboardEvent *e, void *userData) +EM_BOOL WasmCanvas::OnKeyCallback(int eventType, const EmscriptenKeyboardEvent *e, void *userData) { - EmscriptenCanvas* canvas = reinterpret_cast(userData); + WasmCanvas* canvas = reinterpret_cast(userData); int dom_pk_code = emscripten_compute_dom_pk_code(e->code); @@ -188,7 +189,7 @@ EM_BOOL EmscriptenCanvas::OnKeyCallback(int eventType, const EmscriptenKeyboardE printf("%s, key: \"%s\", code: \"%s\" = %s (%d)\n", emscripten_event_type_to_string(eventType), e->key, e->code, emscripten_dom_pk_code_to_string(dom_pk_code), dom_pk_code); - auto key = MapKey(e->code); + auto key = MapEmscriptenKeyCode(e->code); if (eventType == 2) canvas->PostKeyDown(key); @@ -198,7 +199,7 @@ EM_BOOL EmscriptenCanvas::OnKeyCallback(int eventType, const EmscriptenKeyboardE return true; } -EM_BOOL EmscriptenCanvas::OnMouseCallback(int eventType, const EmscriptenMouseEvent *e, void *userData) +EM_BOOL WasmCanvas::OnMouseCallback(int eventType, const EmscriptenMouseEvent *e, void *userData) { /* printf("%s, screen: (%ld,%ld), client: (%ld,%ld),%s%s%s%s button: %hu, buttons: %hu, movement: (%ld,%ld), canvas: (%ld,%ld), target: (%ld, %ld)\n", @@ -229,7 +230,7 @@ EM_BOOL EmscriptenCanvas::OnMouseCallback(int eventType, const EmscriptenMouseEv return EMSCRIPTEN_RESULT_SUCCESS; } -EM_BOOL EmscriptenCanvas::OnWheelCallback(int eventType, const EmscriptenWheelEvent *e, void *userData) +EM_BOOL WasmCanvas::OnWheelCallback(int eventType, const EmscriptenWheelEvent *e, void *userData) { /* printf("%s, screen: (%ld,%ld), client: (%ld,%ld),%s%s%s%s button: %hu, buttons: %hu, canvas: (%ld,%ld), target: (%ld, %ld), delta:(%g,%g,%g), deltaMode:%lu\n", @@ -247,7 +248,7 @@ EM_BOOL EmscriptenCanvas::OnWheelCallback(int eventType, const EmscriptenWheelEv return EMSCRIPTEN_RESULT_SUCCESS; } -void EmscriptenCanvas::CreateEmscriptenCanvas(const CanvasDescriptor& desc) +void WasmCanvas::CreateEmscriptenCanvas(const CanvasDescriptor& desc) { /* Find canvas handle*/ emscripten::val config = emscripten::val::module_property("config"); @@ -275,21 +276,21 @@ void EmscriptenCanvas::CreateEmscriptenCanvas(const CanvasDescriptor& desc) /* Set callbacks */ EMSCRIPTEN_RESULT ret; - ret = emscripten_set_beforeunload_callback(this, EmscriptenCanvas::OnBeforeUnloadCallback); - ret = emscripten_set_resize_callback(canvas_selector.c_str(), this, true, EmscriptenCanvas::OnCanvasResizeCallback); - - ret = emscripten_set_keydown_callback(canvas_selector.c_str(), this, true, EmscriptenCanvas::OnKeyCallback); - ret = emscripten_set_keyup_callback(canvas_selector.c_str(), this, true, EmscriptenCanvas::OnKeyCallback); - - ret = emscripten_set_click_callback(canvas_selector.c_str(), this, true, EmscriptenCanvas::OnMouseCallback); - ret = emscripten_set_mousedown_callback(canvas_selector.c_str(), this, true, EmscriptenCanvas::OnMouseCallback); - ret = emscripten_set_mouseup_callback(canvas_selector.c_str(), this, true, EmscriptenCanvas::OnMouseCallback); - ret = emscripten_set_dblclick_callback(canvas_selector.c_str(), this, true, EmscriptenCanvas::OnMouseCallback); - ret = emscripten_set_mousemove_callback(canvas_selector.c_str(), this, true, EmscriptenCanvas::OnMouseCallback); - ret = emscripten_set_wheel_callback(canvas_selector.c_str(), this, true, EmscriptenCanvas::OnWheelCallback); + ret = emscripten_set_beforeunload_callback(this, WasmCanvas::OnBeforeUnloadCallback); + ret = emscripten_set_resize_callback(canvas_selector.c_str(), this, true, WasmCanvas::OnCanvasResizeCallback); + + ret = emscripten_set_keydown_callback(canvas_selector.c_str(), this, true, WasmCanvas::OnKeyCallback); + ret = emscripten_set_keyup_callback(canvas_selector.c_str(), this, true, WasmCanvas::OnKeyCallback); + + ret = emscripten_set_click_callback(canvas_selector.c_str(), this, true, WasmCanvas::OnMouseCallback); + ret = emscripten_set_mousedown_callback(canvas_selector.c_str(), this, true, WasmCanvas::OnMouseCallback); + ret = emscripten_set_mouseup_callback(canvas_selector.c_str(), this, true, WasmCanvas::OnMouseCallback); + ret = emscripten_set_dblclick_callback(canvas_selector.c_str(), this, true, WasmCanvas::OnMouseCallback); + ret = emscripten_set_mousemove_callback(canvas_selector.c_str(), this, true, WasmCanvas::OnMouseCallback); + ret = emscripten_set_wheel_callback(canvas_selector.c_str(), this, true, WasmCanvas::OnWheelCallback); } -void EmscriptenCanvas::ProcessKeyEvent(/*event, bool down*/) +void WasmCanvas::ProcessKeyEvent(/*event, bool down*/) { /*auto key = MapKey(event); if (down) @@ -298,7 +299,7 @@ void EmscriptenCanvas::ProcessKeyEvent(/*event, bool down*/) PostKeyUp(key);*/ } -void EmscriptenCanvas::ProcessMouseKeyEvent(/*event, bool down*/) +void WasmCanvas::ProcessMouseKeyEvent(/*event, bool down*/) { /*switch (event.button) { @@ -320,7 +321,7 @@ void EmscriptenCanvas::ProcessMouseKeyEvent(/*event, bool down*/) }*/ } -/*void EmscriptenCanvas::ProcessExposeEvent() +/*void WasmCanvas::ProcessExposeEvent() { const Extent2D size { @@ -330,14 +331,14 @@ void EmscriptenCanvas::ProcessMouseKeyEvent(/*event, bool down*/) PostResize(size); }*/ -void EmscriptenCanvas::ProcessClientMessage(/*XClientMessageEvent& event*/) +void WasmCanvas::ProcessClientMessage(/*XClientMessageEvent& event*/) { /*Atom atom = static_cast(event.data.l[0]); if (atom == closeWndAtom_) PostQuit();*/ } -void EmscriptenCanvas::ProcessMotionEvent(/*XMotionEvent& event*/) +void WasmCanvas::ProcessMotionEvent(/*XMotionEvent& event*/) { /*const Offset2D mousePos { event.x, event.y }; PostLocalMotion(mousePos); @@ -345,7 +346,7 @@ void EmscriptenCanvas::ProcessMotionEvent(/*XMotionEvent& event*/) prevMousePos_ = mousePos;*/ } -void EmscriptenCanvas::PostMouseKeyEvent(Key key, bool down) +void WasmCanvas::PostMouseKeyEvent(Key key, bool down) { if (down) PostKeyDown(key); diff --git a/sources/Platform/Wasm/WasmCanvas.h b/sources/Platform/Wasm/WasmCanvas.h index 17102e8b40..167a3d16b8 100644 --- a/sources/Platform/Wasm/WasmCanvas.h +++ b/sources/Platform/Wasm/WasmCanvas.h @@ -1,12 +1,12 @@ /* - * EmscriptenCanvas.h + * WasmCanvas.h * * Copyright (c) 2015 Lukas Hermanns. All rights reserved. * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). */ -#ifndef LLGL_EMSCRIPTEN_CANVAS_H -#define LLGL_EMSCRIPTEN_CANVAS_H +#ifndef LLGL_WASM_CANVAS_H +#define LLGL_WASM_CANVAS_H #include @@ -21,12 +21,12 @@ namespace LLGL { -class EmscriptenCanvas : public Canvas +class WasmCanvas : public Canvas { public: - EmscriptenCanvas(const CanvasDescriptor& desc); + WasmCanvas(const CanvasDescriptor& desc); bool GetNativeHandle(void* nativeHandle, std::size_t nativeHandleSize) override; diff --git a/sources/Platform/Wasm/WasmDebug.cpp b/sources/Platform/Wasm/WasmDebug.cpp index b0dc9de5b4..56c3df3535 100644 --- a/sources/Platform/Wasm/WasmDebug.cpp +++ b/sources/Platform/Wasm/WasmDebug.cpp @@ -1,5 +1,5 @@ /* - * LinuxDebug.cpp + * WasmDebug.cpp * * Copyright (c) 2015 Lukas Hermanns. All rights reserved. * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). diff --git a/sources/Platform/Wasm/WasmDebug.h b/sources/Platform/Wasm/WasmDebug.h index 52fc4062e8..4f087d665c 100644 --- a/sources/Platform/Wasm/WasmDebug.h +++ b/sources/Platform/Wasm/WasmDebug.h @@ -1,12 +1,12 @@ /* - * LinuxDebug.h + * WasmDebug.h * * Copyright (c) 2015 Lukas Hermanns. All rights reserved. * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). */ -#ifndef LLGL_EMSCRIPTEN_DEBUG_H -#define LLGL_EMSCRIPTEN_DEBUG_H +#ifndef LLGL_WASM_DEBUG_H +#define LLGL_WASM_DEBUG_H #include diff --git a/sources/Platform/Wasm/WasmDisplay.cpp b/sources/Platform/Wasm/WasmDisplay.cpp index 504804e8d3..96d0c1f665 100644 --- a/sources/Platform/Wasm/WasmDisplay.cpp +++ b/sources/Platform/Wasm/WasmDisplay.cpp @@ -1,11 +1,11 @@ /* - * EmscriptenDisplay.cpp + * WasmDisplay.cpp * * Copyright (c) 2015 Lukas Hermanns. All rights reserved. * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). */ -#include "EmscriptenDisplay.h" +#include "WasmDisplay.h" #include "../../Core/CoreUtils.h" @@ -61,57 +61,50 @@ Offset2D Display::GetCursorPosition() /* - * EmscriptenDisplay class + * WasmDisplay class */ -EmscriptenDisplay::EmscriptenDisplay(int screenIndex) : - screen_ { screenIndex } -{ -} - -bool EmscriptenDisplay::IsPrimary() const +bool WasmDisplay::IsPrimary() const { return true; } -UTF8String EmscriptenDisplay::GetDeviceName() const +UTF8String WasmDisplay::GetDeviceName() const { return UTF8String{ "device name" }; } -Offset2D EmscriptenDisplay::GetOffset() const +Offset2D WasmDisplay::GetOffset() const { return Offset2D{}; } -float EmscriptenDisplay::GetScale() const +float WasmDisplay::GetScale() const { return 1.0f; // dummy } -bool EmscriptenDisplay::ResetDisplayMode() +bool WasmDisplay::ResetDisplayMode() { return false; } -bool EmscriptenDisplay::SetDisplayMode(const DisplayMode& displayMode) +bool WasmDisplay::SetDisplayMode(const DisplayMode& displayMode) { return false; } -DisplayMode EmscriptenDisplay::GetDisplayMode() const +DisplayMode WasmDisplay::GetDisplayMode() const { DisplayMode displayMode; return displayMode; } -std::vector EmscriptenDisplay::GetSupportedDisplayModes() const +std::vector WasmDisplay::GetSupportedDisplayModes() const { std::vector displayModes; - - DisplayMode displayMode; - + DisplayMode displayMode; // todo return displayModes; } diff --git a/sources/Platform/Wasm/WasmDisplay.h b/sources/Platform/Wasm/WasmDisplay.h index 004e53842f..4a189039ce 100644 --- a/sources/Platform/Wasm/WasmDisplay.h +++ b/sources/Platform/Wasm/WasmDisplay.h @@ -1,12 +1,12 @@ /* - * LinuxDisplay.h + * WasmDisplay.h * * Copyright (c) 2015 Lukas Hermanns. All rights reserved. * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). */ -#ifndef LLGL_EMSCRIPTEN_DISPLAY_H -#define LLGL_EMSCRIPTEN_DISPLAY_H +#ifndef LLGL_WASM_DISPLAY_H +#define LLGL_WASM_DISPLAY_H #include @@ -16,12 +16,12 @@ namespace LLGL { -class EmscriptenDisplay : public Display +class WasmDisplay : public Display { public: - EmscriptenDisplay(int screenIndex); + WasmDisplay() = default; bool IsPrimary() const override; @@ -36,10 +36,6 @@ class EmscriptenDisplay : public Display std::vector GetSupportedDisplayModes() const override; - private: - - int screen_ = 0; - }; diff --git a/sources/Platform/Wasm/WasmKeyCodes.cpp b/sources/Platform/Wasm/WasmKeyCodes.cpp index 5153e51760..1a4cdc468d 100644 --- a/sources/Platform/Wasm/WasmKeyCodes.cpp +++ b/sources/Platform/Wasm/WasmKeyCodes.cpp @@ -1,11 +1,11 @@ /* - * MapKey.cpp (Emscripten) + * WasmKeyCodes.cpp * * Copyright (c) 2015 Lukas Hermanns. All rights reserved. * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). */ -#include "MapKey.h" +#include "WasmKeyCodes.h" #include #include #include @@ -187,7 +187,7 @@ static std::map g_emscriptenWindowKeyCodeMap = GenerateEmscriptenKeyCo #undef KEYPAIR -Key MapKey(const char* keyEvent) +Key MapEmscriptenKeyCode(const char* keyEvent) { int keyCode = emscripten_compute_dom_pk_code(keyEvent); auto it = g_emscriptenWindowKeyCodeMap.find(keyCode); diff --git a/sources/Platform/Wasm/WasmKeyCodes.h b/sources/Platform/Wasm/WasmKeyCodes.h index c65536a845..ed717c1167 100644 --- a/sources/Platform/Wasm/WasmKeyCodes.h +++ b/sources/Platform/Wasm/WasmKeyCodes.h @@ -1,12 +1,12 @@ /* - * MapKey.h + * WasmKeyCodes.h * * Copyright (c) 2015 Lukas Hermanns. All rights reserved. * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). */ -#ifndef LLGL_MAP_KEY_H -#define LLGL_MAP_KEY_H +#ifndef LLGL_WASM_KEY_CODES_H +#define LLGL_WASM_KEY_CODES_H #include @@ -16,7 +16,7 @@ namespace LLGL { -Key MapKey(const char* keyEvent); +Key MapEmscriptenKeyCode(const char* keyEvent); } // /namespace LLGL diff --git a/sources/Platform/Wasm/WasmModule.cpp b/sources/Platform/Wasm/WasmModule.cpp index 4840112ea6..3fcbb2ac0e 100644 --- a/sources/Platform/Wasm/WasmModule.cpp +++ b/sources/Platform/Wasm/WasmModule.cpp @@ -1,11 +1,11 @@ /* - * LinuxModule.cpp + * WasmModule.cpp * * Copyright (c) 2015 Lukas Hermanns. All rights reserved. * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). */ -#include "EmscriptenModule.h" +#include "WasmModule.h" #include "../../Core/CoreUtils.h" #include "../../Core/Exception.h" #include @@ -67,11 +67,11 @@ bool Module::IsAvailable(const char* moduleFilename) std::unique_ptr Module::Load(const char* moduleFilename, Report* report) { - std::unique_ptr module = MakeUnique(moduleFilename, report); + std::unique_ptr module = MakeUnique(moduleFilename, report); return (module->IsValid() ? std::move(module) : nullptr); } -EmscriptenModule::EmscriptenModule(const char* moduleFilename, Report* report) +WasmModule::WasmModule(const char* moduleFilename, Report* report) { /* Open Linux shared library */ handle_ = dlopen(moduleFilename, RTLD_LAZY); @@ -92,13 +92,13 @@ EmscriptenModule::EmscriptenModule(const char* moduleFilename, Report* report) } } -EmscriptenModule::~EmscriptenModule() +WasmModule::~WasmModule() { if (handle_ != nullptr) dlclose(handle_); } -void* EmscriptenModule::LoadProcedure(const char* procedureName) +void* WasmModule::LoadProcedure(const char* procedureName) { /* Get procedure address from library module and return it as raw-pointer */ return dlsym(handle_, procedureName); diff --git a/sources/Platform/Wasm/WasmModule.h b/sources/Platform/Wasm/WasmModule.h index f9a09ce3b9..2fa5093f14 100644 --- a/sources/Platform/Wasm/WasmModule.h +++ b/sources/Platform/Wasm/WasmModule.h @@ -1,12 +1,12 @@ /* - * LinuxModule.h + * WasmModule.h * * Copyright (c) 2015 Lukas Hermanns. All rights reserved. * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). */ -#ifndef LLGL_EMSCRIPTEN_MODULE_H -#define LLGL_EMSCRIPTEN_MODULE_H +#ifndef LLGL_WASM_MODULE_H +#define LLGL_WASM_MODULE_H #include "../Module.h" @@ -16,13 +16,13 @@ namespace LLGL { -class EmscriptenModule : public Module +class WasmModule : public Module { public: - EmscriptenModule(const char* moduleFilename, Report* report = nullptr); - ~EmscriptenModule(); + WasmModule(const char* moduleFilename, Report* report = nullptr); + ~WasmModule(); void* LoadProcedure(const char* procedureName) override; diff --git a/sources/Platform/Wasm/WasmPath.cpp b/sources/Platform/Wasm/WasmPath.cpp index 2c0709e541..dd0ea6fdf5 100644 --- a/sources/Platform/Wasm/WasmPath.cpp +++ b/sources/Platform/Wasm/WasmPath.cpp @@ -1,5 +1,5 @@ /* - * LinuxPath.cpp + * WasmPath.cpp * * Copyright (c) 2015 Lukas Hermanns. All rights reserved. * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). diff --git a/sources/Platform/Wasm/WasmTimer.cpp b/sources/Platform/Wasm/WasmTimer.cpp index de7d1c3b89..0142f4a811 100644 --- a/sources/Platform/Wasm/WasmTimer.cpp +++ b/sources/Platform/Wasm/WasmTimer.cpp @@ -1,5 +1,5 @@ /* - * EmscriptenTimer.cpp + * WasmTimer.cpp * * Copyright (c) 2015 Lukas Hermanns. All rights reserved. * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). diff --git a/sources/Renderer/OpenGL/CMakeLists.txt b/sources/Renderer/OpenGL/CMakeLists.txt index e269e6a6a6..1447f1658f 100644 --- a/sources/Renderer/OpenGL/CMakeLists.txt +++ b/sources/Renderer/OpenGL/CMakeLists.txt @@ -100,8 +100,8 @@ elseif(UNIX) find_source_files(FilesRendererGLPlatform CXX ${PROJECT_SOURCE_DIR}/Platform/Android) find_source_files(FilesIncludeGLPlatform INC ${BACKEND_INCLUDE_DIR}/OpenGL/Android) elseif(EMSCRIPTEN) - find_source_files(FilesRendererGLPlatform CXX ${PROJECT_SOURCE_DIR}/Platform/Emscripten) - find_source_files(FilesIncludeGLPlatform INC ${BACKEND_INCLUDE_DIR}/OpenGL/Emscripten) + find_source_files(FilesRendererGLPlatform CXX ${PROJECT_SOURCE_DIR}/Platform/Wasm) + find_source_files(FilesIncludeGLPlatform INC ${BACKEND_INCLUDE_DIR}/OpenGL/Wasm) else() find_source_files(FilesRendererGLPlatform CXX ${PROJECT_SOURCE_DIR}/Platform/Linux) find_source_files(FilesIncludeGLPlatform INC ${BACKEND_INCLUDE_DIR}/OpenGL/Linux) diff --git a/sources/Renderer/OpenGL/Platform/Wasm/WasmGLContext.cpp b/sources/Renderer/OpenGL/Platform/Wasm/WasmGLContext.cpp index a8cf0cb4df..3d2621104e 100644 --- a/sources/Renderer/OpenGL/Platform/Wasm/WasmGLContext.cpp +++ b/sources/Renderer/OpenGL/Platform/Wasm/WasmGLContext.cpp @@ -1,11 +1,11 @@ /* - * EmscriptenGLContext.cpp + * WasmGLContext.cpp * * Copyright (c) 2015 Lukas Hermanns. All rights reserved. * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). */ -#include "EmscriptenGLContext.h" +#include "WasmGLContext.h" #include "../../../CheckedCast.h" #include "../../../StaticAssertions.h" #include "../../../../Core/CoreUtils.h" @@ -31,36 +31,36 @@ std::unique_ptr GLContext::Create( GLContext* sharedContext, const ArrayView& /*customNativeHandle*/) { - EmscriptenGLContext* sharedContextEGL = (sharedContext != nullptr ? LLGL_CAST(EmscriptenGLContext*, sharedContext) : nullptr); - return MakeUnique(pixelFormat, profile, surface, sharedContextEGL); + WasmGLContext* sharedContextEGL = (sharedContext != nullptr ? LLGL_CAST(WasmGLContext*, sharedContext) : nullptr); + return MakeUnique(pixelFormat, profile, surface, sharedContextEGL); } /* - * EmscriptenGLContext class + * WasmGLContext class */ -EmscriptenGLContext::EmscriptenGLContext( +WasmGLContext::WasmGLContext( const GLPixelFormat& pixelFormat, const RendererConfigurationOpenGL& profile, Surface& surface, - EmscriptenGLContext* sharedContext) + WasmGLContext* sharedContext) { CreateContext(pixelFormat, profile, sharedContext); } -EmscriptenGLContext::~EmscriptenGLContext() +WasmGLContext::~WasmGLContext() { DeleteContext(); } -int EmscriptenGLContext::GetSamples() const +int WasmGLContext::GetSamples() const { int samples_ = 4; return samples_; } -bool EmscriptenGLContext::GetNativeHandle(void* nativeHandle, std::size_t nativeHandleSize) const +bool WasmGLContext::GetNativeHandle(void* nativeHandle, std::size_t nativeHandleSize) const { if (nativeHandle != nullptr && nativeHandleSize == sizeof(OpenGL::RenderSystemNativeHandle)) { @@ -76,12 +76,12 @@ bool EmscriptenGLContext::GetNativeHandle(void* nativeHandle, std::size_t native * ======= Private: ======= */ -bool EmscriptenGLContext::SetSwapInterval(int interval) +bool WasmGLContext::SetSwapInterval(int interval) { return true; } -void EmscriptenGLContext::CreateContext(const GLPixelFormat& pixelFormat, const RendererConfigurationOpenGL& profile, EmscriptenGLContext* sharedContext) +void WasmGLContext::CreateContext(const GLPixelFormat& pixelFormat, const RendererConfigurationOpenGL& profile, WasmGLContext* sharedContext) { EmscriptenWebGLContextAttributes attrs; emscripten_webgl_init_context_attributes(&attrs); @@ -106,7 +106,7 @@ void EmscriptenGLContext::CreateContext(const GLPixelFormat& pixelFormat, const EMSCRIPTEN_RESULT res = emscripten_webgl_make_context_current(context_); } -void EmscriptenGLContext::DeleteContext() +void WasmGLContext::DeleteContext() { //destroy context_ } diff --git a/sources/Renderer/OpenGL/Platform/Wasm/WasmGLContext.h b/sources/Renderer/OpenGL/Platform/Wasm/WasmGLContext.h index 69399029a9..7133a9ab28 100644 --- a/sources/Renderer/OpenGL/Platform/Wasm/WasmGLContext.h +++ b/sources/Renderer/OpenGL/Platform/Wasm/WasmGLContext.h @@ -1,12 +1,12 @@ /* - * EmscriptenGLContext.h + * WasmGLContext.h * * Copyright (c) 2015 Lukas Hermanns. All rights reserved. * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). */ -#ifndef LLGL_EMSCRIPTEN_CONTEXT_H -#define LLGL_EMSCRIPTEN_CONTEXT_H +#ifndef LLGL_WASM_CONTEXT_H +#define LLGL_WASM_CONTEXT_H #include "../GLContext.h" @@ -19,18 +19,18 @@ namespace LLGL // Implementation of the interface for Android and wrapper for a native EGL context. -class EmscriptenGLContext : public GLContext +class WasmGLContext : public GLContext { public: - EmscriptenGLContext( + WasmGLContext( const GLPixelFormat& pixelFormat, const RendererConfigurationOpenGL& profile, Surface& surface, - EmscriptenGLContext* sharedContext + WasmGLContext* sharedContext ); - ~EmscriptenGLContext(); + ~WasmGLContext(); int GetSamples() const override; @@ -51,7 +51,7 @@ class EmscriptenGLContext : public GLContext void CreateContext( const GLPixelFormat& pixelFormat, const RendererConfigurationOpenGL& profile, - EmscriptenGLContext* sharedContext + WasmGLContext* sharedContext ); void DeleteContext(); diff --git a/sources/Renderer/OpenGL/Platform/Wasm/WasmGLSwapChainContext.cpp b/sources/Renderer/OpenGL/Platform/Wasm/WasmGLSwapChainContext.cpp index 3edef6184f..25a74b5b6e 100644 --- a/sources/Renderer/OpenGL/Platform/Wasm/WasmGLSwapChainContext.cpp +++ b/sources/Renderer/OpenGL/Platform/Wasm/WasmGLSwapChainContext.cpp @@ -1,11 +1,11 @@ /* - * EmscriptenGLSwapChainContext.cpp + * WasmGLSwapChainContext.cpp * * Copyright (c) 2015 Lukas Hermanns. All rights reserved. * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). */ -#include "EmscriptenGLSwapChainContext.h" +#include "WasmGLSwapChainContext.h" #include "../../../../Core/CoreUtils.h" #include "../../../../Core/Exception.h" #include @@ -21,20 +21,20 @@ namespace LLGL std::unique_ptr GLSwapChainContext::Create(GLContext& context, Surface& surface) { - return MakeUnique(static_cast(context), surface); + return MakeUnique(static_cast(context), surface); } bool GLSwapChainContext::MakeCurrentUnchecked(GLSwapChainContext* context) { - return EmscriptenGLSwapChainContext::MakeCurrentEGLContext(static_cast(context)); + return WasmGLSwapChainContext::MakeCurrentEGLContext(static_cast(context)); } /* - * EmscriptenGLSwapChainContext class + * WasmGLSwapChainContext class */ -EmscriptenGLSwapChainContext::EmscriptenGLSwapChainContext(EmscriptenGLContext& context, Surface& surface) : +WasmGLSwapChainContext::WasmGLSwapChainContext(WasmGLContext& context, Surface& surface) : GLSwapChainContext { context }, context_ { context.GetWebGLContext() } { @@ -46,28 +46,28 @@ EmscriptenGLSwapChainContext::EmscriptenGLSwapChainContext(EmscriptenGLContext& LLGL_TRAP("GetWebGLContext failed"); } -EmscriptenGLSwapChainContext::~EmscriptenGLSwapChainContext() +WasmGLSwapChainContext::~WasmGLSwapChainContext() { //eglDestroySurface(display_, surface_); } -bool EmscriptenGLSwapChainContext::HasDrawable() const +bool WasmGLSwapChainContext::HasDrawable() const { return true; // dummy } -bool EmscriptenGLSwapChainContext::SwapBuffers() +bool WasmGLSwapChainContext::SwapBuffers() { // do nothing return true; } -void EmscriptenGLSwapChainContext::Resize(const Extent2D& resolution) +void WasmGLSwapChainContext::Resize(const Extent2D& resolution) { // do nothing (WebGL context does not need to be resized) } -bool EmscriptenGLSwapChainContext::MakeCurrentEGLContext(EmscriptenGLSwapChainContext* context) +bool WasmGLSwapChainContext::MakeCurrentEGLContext(WasmGLSwapChainContext* context) { return true; EMSCRIPTEN_RESULT res = emscripten_webgl_make_context_current(context->context_); diff --git a/sources/Renderer/OpenGL/Platform/Wasm/WasmGLSwapChainContext.h b/sources/Renderer/OpenGL/Platform/Wasm/WasmGLSwapChainContext.h index b2dbfbc085..c7f675a0cf 100644 --- a/sources/Renderer/OpenGL/Platform/Wasm/WasmGLSwapChainContext.h +++ b/sources/Renderer/OpenGL/Platform/Wasm/WasmGLSwapChainContext.h @@ -1,32 +1,32 @@ /* - * EmscriptenGLSwapChainContext.h + * WasmGLSwapChainContext.h * * Copyright (c) 2015 Lukas Hermanns. All rights reserved. * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). */ -#ifndef LLGL_EMSCRIPTEN_GL_SWAP_CHAIN_CONTEXT_H -#define LLGL_EMSCRIPTEN_GL_SWAP_CHAIN_CONTEXT_H +#ifndef LLGL_WASM_GL_SWAP_CHAIN_CONTEXT_H +#define LLGL_WASM_GL_SWAP_CHAIN_CONTEXT_H #include "../GLSwapChainContext.h" #include "../../OpenGL.h" -#include "EmscriptenGLContext.h" +#include "WasmGLContext.h" namespace LLGL { class Surface; -class EmscriptenGLContext; +class WasmGLContext; -class EmscriptenGLSwapChainContext final : public GLSwapChainContext +class WasmGLSwapChainContext final : public GLSwapChainContext { public: - EmscriptenGLSwapChainContext(EmscriptenGLContext& context, Surface& surface); - ~EmscriptenGLSwapChainContext(); + WasmGLSwapChainContext(WasmGLContext& context, Surface& surface); + ~WasmGLSwapChainContext(); bool HasDrawable() const override; bool SwapBuffers() override; @@ -34,7 +34,7 @@ class EmscriptenGLSwapChainContext final : public GLSwapChainContext public: - static bool MakeCurrentEGLContext(EmscriptenGLSwapChainContext* context); + static bool MakeCurrentEGLContext(WasmGLSwapChainContext* context); private: diff --git a/sources/Renderer/OpenGL/WebGLProfile/WebGL.h b/sources/Renderer/OpenGL/WebGLProfile/WebGL.h index b03eeffb02..266b7e3933 100644 --- a/sources/Renderer/OpenGL/WebGLProfile/WebGL.h +++ b/sources/Renderer/OpenGL/WebGLProfile/WebGL.h @@ -11,7 +11,7 @@ #include -#if defined(LLGL_OS_EMSCRIPTEN) +#if defined(LLGL_OS_WASM) # define GL_GLEXT_PROTOTYPES 1 # define EGL_EGLEXT_PROTOTYPES 1 # include From d412cfb818472e351d34dbe9aef947f8b69ddff7 Mon Sep 17 00:00:00 2001 From: Laura Hermanns Date: Wed, 21 Aug 2024 23:24:33 -0400 Subject: [PATCH 09/15] [Wasm] Started with build script for Wasm platform. - Added BuildWasm.sh bash script. - Added HTML5 template file for examples. - Added wasm.svg logo (CC0 license). - Fixed remaining identifiers that were renamed from Emscripten to Wasm. --- BuildWasm.sh | 221 ++++++++++++++++++ README.md | 3 +- docu/Icons/wasm.svg | 1 + docu/Icons/wasm.svg.license.txt | 5 + examples/Cpp/ExampleBase/ExampleBase.cpp | 2 +- examples/Cpp/ExampleBase/FileUtils.cpp | 5 + examples/Cpp/ExampleBase/Wasm/index.html | 48 ++++ sources/Renderer/OpenGL/GLCore.cpp | 2 +- .../OpenGL/GLCoreProfile/OpenGLCore.h | 2 - .../GLESProfile/GLESExtensionLoader.cpp | 2 +- .../WebGLProfile/WebGLExtensionLoader.cpp | 2 +- 11 files changed, 286 insertions(+), 7 deletions(-) create mode 100644 BuildWasm.sh create mode 100644 docu/Icons/wasm.svg create mode 100644 docu/Icons/wasm.svg.license.txt create mode 100644 examples/Cpp/ExampleBase/Wasm/index.html diff --git a/BuildWasm.sh b/BuildWasm.sh new file mode 100644 index 0000000000..eda252b62f --- /dev/null +++ b/BuildWasm.sh @@ -0,0 +1,221 @@ +#!/bin/bash + +SOURCE_DIR=$PWD +OUTPUT_DIR="build_wasm" +CLEAR_CACHE=0 +ENABLE_EXAMPLES="ON" +ENABLE_TESTS="ON" +BUILD_TYPE="Release" +PROJECT_ONLY=0 +VERBOSE=0 +GENERATOR="CodeBlocks - Unix Makefiles" + +# Check whether we are on a Linux distribution or MSYS on Windows +print_help() +{ + echo "USAGE:" + echo " BuildWasm.sh OPTIONS* [OUTPUT_DIR]" + echo "OPTIONS:" + echo " -c, --clear-cache ......... Clear CMake cache and rebuild" + echo " -h, --help ................ Print this help documentation and exit" + echo " -d, --debug ............... Configure Debug build (default is Release)" + echo " -p, --project-only [=G] ... Build project with CMake generator (default is CodeBlocks)" + echo " --no-examples ............. Exclude example projects" + echo " --no-tests ................ Exclude test projects" + echo "NOTES:" + echo " Default output directory is '$OUTPUT_DIR'" +} + +# Parse arguments +for ARG in "$@"; do + if [ "$ARG" = "-h" ] || [ "$ARG" = "--help" ]; then + print_help + exit 0 + elif [ "$ARG" = "-c" ] || [ "$ARG" = "--clear-cache" ]; then + CLEAR_CACHE=1 + elif [ "$ARG" = "-d" ] || [ "$ARG" = "--debug" ]; then + BUILD_TYPE="Debug" + elif [ "$ARG" = "-p" ] || [ "$ARG" = "--project-only" ]; then + PROJECT_ONLY=1 + elif [[ "$ARG" == -p=* ]]; then + PROJECT_ONLY=1 + GENERATOR="${ARG:3}" + elif [[ "$ARG" == --project-only=* ]]; then + PROJECT_ONLY=1 + GENERATOR="${ARG:15}" + elif [ "$ARG" = "-v" ] || [ "$ARG" = "--verbose" ]; then + VERBOSE=1 + elif [ "$ARG" = "--null" ]; then + ENABLE_NULL="ON" + elif [ "$ARG" = "--vulkan" ]; then + ENABLE_VULKAN="ON" + elif [ "$ARG" = "--no-examples" ]; then + ENABLE_EXAMPLES="OFF" + elif [ "$ARG" = "--no-tests" ]; then + ENABLE_TESTS="OFF" + elif [ ! "$ARG" = "-msys" ]; then + OUTPUT_DIR="$ARG" + fi +done + +# Ensure we are inside the repository folder +if [ ! -f "CMakeLists.txt" ]; then + echo "Error: File not found: CMakeLists.txt" + exit 1 +fi + +# Make output build folder +if [ $CLEAR_CACHE = 1 ] && [ -d "$OUTPUT_DIR" ]; then + rm -rf "$OUTPUT_DIR" +fi + +if [ ! -d "$OUTPUT_DIR" ]; then + mkdir "$OUTPUT_DIR" +fi + +# Checkout external depenencies +GAUSSIAN_LIB_DIR="GaussianLib/include" + +if [ -f "$SOURCE_DIR/external/$GAUSSIAN_LIB_DIR/Gauss/Gauss.h" ]; then + GAUSSIAN_LIB_DIR=$(realpath "$SOURCE_DIR/external/$GAUSSIAN_LIB_DIR") +else + if [ ! -d "$OUTPUT_DIR/$GAUSSIAN_LIB_DIR" ]; then + (cd "$OUTPUT_DIR" && git clone https://github.com/LukasBanana/GaussianLib.git) + fi + GAUSSIAN_LIB_DIR=$(realpath "$OUTPUT_DIR/$GAUSSIAN_LIB_DIR") +fi + +# Print additional information if in verbose mode +if [ $VERBOSE -eq 1 ]; then + echo "GAUSSIAN_LIB_DIR=$GAUSSIAN_LIB_DIR" + if [ $PROJECT_ONLY -eq 0 ]; then + echo "BUILD_TYPE=$BUILD_TYPE" + else + echo "GENERATOR=$GENERATOR" + fi +fi + +# Find Emscripten SDK +if [ -z "$EMSDK" ]; then + echo "Error: Missing EMSDK environment variable. Run 'source /emsdk_env.sh' to fix it." + exit 1 +fi + +EMSCRIPTEN_CMAKE_TOOLCHAIN="$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake" + +if [ ! -f "$EMSCRIPTEN_CMAKE_TOOLCHAIN" ]; then + echo "Error: Could not find file $EMSCRIPTEN_CMAKE_TOOLCHAIN" + exit 1 +fi + +# Build into output directory (this syntax requried CMake 3.13+) +OPTIONS=( + -DCMAKE_TOOLCHAIN_FILE="$EMSCRIPTEN_CMAKE_TOOLCHAIN" + -DLLGL_BUILD_RENDERER_WEBGL=ON + -DLLGL_GL_ENABLE_OPENGL2X=OFF + -DLLGL_BUILD_RENDERER_NULL=OFF + -DLLGL_BUILD_RENDERER_VULKAN=OFF + -DLLGL_BUILD_RENDERER_DIRECT3D11=OFF + -DLLGL_BUILD_RENDERER_DIRECT3D12=OFF + -DLLGL_BUILD_EXAMPLES=$ENABLE_EXAMPLES + -DLLGL_BUILD_TESTS=$ENABLE_TESTS + -DLLGL_BUILD_STATIC_LIB=ON + -DGaussLib_INCLUDE_DIR:STRING="$GAUSSIAN_LIB_DIR" + -S "$SOURCE_DIR" + -B "$OUTPUT_DIR" +) + +if [ $PROJECT_ONLY -eq 0 ]; then + cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ${OPTIONS[@]} + cmake --build "$OUTPUT_DIR" -- -j 20 +else + cmake ${OPTIONS[@]} -G "$GENERATOR" +fi + +# Generate HTML pages +generate_html5_page() +{ + CURRENT_PROJECT=$1 + + echo "Generate HTML5 page: $CURRENT_PROJECT" + + # Get source folder + ASSET_SOURCE_DIR="$SOURCE_DIR/examples/Media" + PROJECT_SOURCE_DIR="$SOURCE_DIR/examples/Cpp" + if [[ "$CURRENT_PROJECT" == *D ]]; then + PROJECT_SOURCE_DIR="$PROJECT_SOURCE_DIR/${CURRENT_PROJECT:0:-1}" + else + PROJECT_SOURCE_DIR="$PROJECT_SOURCE_DIR/$CURRENT_PROJECT" + fi + + # Get destination folder + HTML5_ROOT="${OUTPUT_DIR}/html5/Example_$CURRENT_PROJECT" + BIN_ROOT=${OUTPUT_DIR}/build + + # Create folder structure + mkdir -p "$HTML5_ROOT" + BASE_FILENAME="Example_$CURRENT_PROJECT" + cp "$SOURCE_DIR/examples/Cpp/ExampleBase/Wasm/index.html" "$HTML5_ROOT/index.html" + cp "$BIN_ROOT/$BASE_FILENAME.js" "$HTML5_ROOT/$BASE_FILENAME.js" + cp "$BIN_ROOT/$BASE_FILENAME.wasm" "$HTML5_ROOT/$BASE_FILENAME.wasm" + cp "$BIN_ROOT/$BASE_FILENAME.worker.js" "$HTML5_ROOT/$BASE_FILENAME.worker.js" + + # Replace meta data + sed -i "s/LLGL_EXAMPLE_NAME/${CURRENT_PROJECT}/" "$HTML5_ROOT/index.html" + sed -i "s/LLGL_EXAMPLE_PROJECT/Example_${CURRENT_PROJECT}/" "$HTML5_ROOT/index.html" + + # Find all required assets in Android.assets.txt file of respective project directory and copy them into app folder + ASSET_DIR="$HTML5_ROOT/assets" + mkdir -p "$ASSET_DIR" + + ASSET_LIST_FILE="$PROJECT_SOURCE_DIR/Android.assets.txt" + if [ -f "$ASSET_LIST_FILE" ]; then + # Read Android.asset.txt file line-by-line into array and make sure '\r' character is not present (on Win32 platform) + readarray -t ASSET_FILTERS < <(tr -d '\r' < "$ASSET_LIST_FILE") + ASSET_FILES=() + for FILTER in ${ASSET_FILTERS[@]}; do + for FILE in $ASSET_SOURCE_DIR/$FILTER; do + ASSET_FILES+=( "$FILE" ) + done + done + + # Copy all asset file into destination folder + for FILE in ${ASSET_FILES[@]}; do + if [ $VERBOSE -eq 1 ]; then + echo "Copy asset: $(basename $FILE)" + fi + cp "$FILE" "$ASSET_DIR/$(basename $FILE)" + done + fi + + # Find all shaders and copy them into app folder + for FILE in $PROJECT_SOURCE_DIR/*.vert \ + $PROJECT_SOURCE_DIR/*.frag; do + if [ -f "$FILE" ]; then + if [ $VERBOSE -eq 1 ]; then + echo "Copy shader: $(basename $FILE)" + fi + cp "$FILE" "$ASSET_DIR/$(basename $FILE)" + fi + done +} + +if [ $PROJECT_ONLY -eq 0 ] && [ $ENABLE_EXAMPLES == "ON" ]; then + + BIN_FILE_BASE="${OUTPUT_DIR}/build/Example_" + BIN_FILE_BASE_LEN=${#BIN_FILE_BASE} + + EXAMPLE_BIN_FILES_D=(${BIN_FILE_BASE}*D.wasm) + if [ -z "$EXAMPLE_BIN_FILES_D" ]; then + EXAMPLE_BIN_FILES=(${BIN_FILE_BASE}*.wasm) + else + EXAMPLE_BIN_FILES=(${EXAMPLE_BIN_FILES_D[@]}) + fi + + for BIN_FILE in ${EXAMPLE_BIN_FILES[@]}; do + BIN_FILE_LEN=${#BIN_FILE} + PROJECT_NAME=${BIN_FILE:BIN_FILE_BASE_LEN:BIN_FILE_LEN-BIN_FILE_BASE_LEN-5} + generate_html5_page $PROJECT_NAME + done + +fi diff --git a/README.md b/README.md index 4542fb8378..d73c1d1be2 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ with Introduction, Hello Triangle Tutorial, and Extensibility Example with [GLFW ## Platform Support -| Platform | CI | D3D12 | D3D11 | Vulkan | GL/GLES3 | Metal | +| Platform | CI | D3D12 | D3D11 | Vulkan | GL/GLES/WebGL | Metal | |----------|:--:|:-----:|:-----:|:------:|:--------:|:-----:| | Windows |

[![MSVC16+ CI](https://github.com/LukasBanana/LLGL/actions/workflows/ci_windows.yml/badge.svg)](https://github.com/LukasBanana/LLGL/actions/workflows/ci_windows.yml)

[![MSVC14 CI](https://ci.appveyor.com/api/projects/status/j09x8n07u3byfky0?svg=true)](https://ci.appveyor.com/project/LukasBanana/llgl)

| :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | N/A | | UWP | [![UWP CI](https://github.com/LukasBanana/LLGL/actions/workflows/ci_uwp.yml/badge.svg)](https://github.com/LukasBanana/LLGL/actions/workflows/ci_uwp.yml) | :heavy_check_mark: | :heavy_check_mark: | N/A | N/A | N/A | @@ -38,6 +38,7 @@ with Introduction, Hello Triangle Tutorial, and Extensibility Example with [GLFW | macOS | [![macOS CI](https://github.com/LukasBanana/LLGL/actions/workflows/ci_macos.yml/badge.svg)](https://github.com/LukasBanana/LLGL/actions/workflows/ci_macos.yml) | N/A | N/A | N/A | :heavy_check_mark: | :heavy_check_mark: | | iOS | [![iOS CI](https://github.com/LukasBanana/LLGL/actions/workflows/ci_ios.yml/badge.svg)](https://github.com/LukasBanana/LLGL/actions/workflows/ci_ios.yml) | N/A | N/A | N/A | :heavy_check_mark: | :heavy_check_mark: | | Android | [![Android CI](https://github.com/LukasBanana/LLGL/actions/workflows/ci_android.yml/badge.svg)](https://github.com/LukasBanana/LLGL/actions/workflows/ci_android.yml) | N/A | N/A | :heavy_check_mark: | :heavy_check_mark: | N/A | +| Wasm | | N/A | N/A | N/A | :heavy_check_mark: | N/A | ## Build Notes diff --git a/docu/Icons/wasm.svg b/docu/Icons/wasm.svg new file mode 100644 index 0000000000..fef0021b3a --- /dev/null +++ b/docu/Icons/wasm.svg @@ -0,0 +1 @@ +web-assembly-icon-black \ No newline at end of file diff --git a/docu/Icons/wasm.svg.license.txt b/docu/Icons/wasm.svg.license.txt new file mode 100644 index 0000000000..98fc3bed9b --- /dev/null +++ b/docu/Icons/wasm.svg.license.txt @@ -0,0 +1,5 @@ +License: + CC0-1.0 license + +Weblink: + https://github.com/carlosbaraza/web-assembly-logo/blob/master/dist/icon/web-assembly-icon-black.svg \ No newline at end of file diff --git a/examples/Cpp/ExampleBase/ExampleBase.cpp b/examples/Cpp/ExampleBase/ExampleBase.cpp index b237bb10ff..e6f976ff12 100644 --- a/examples/Cpp/ExampleBase/ExampleBase.cpp +++ b/examples/Cpp/ExampleBase/ExampleBase.cpp @@ -152,7 +152,7 @@ static constexpr const char* GetDefaultRendererModule() return "Metal"; #elif defined LLGL_OS_ANDROID return "OpenGLES3"; - #elif defined LLGL_OS_EMSCRIPTEN + #elif defined LLGL_OS_WASM return "WebGL"; #else return "OpenGL"; diff --git a/examples/Cpp/ExampleBase/FileUtils.cpp b/examples/Cpp/ExampleBase/FileUtils.cpp index 751a4ab31b..5ff6c59b50 100644 --- a/examples/Cpp/ExampleBase/FileUtils.cpp +++ b/examples/Cpp/ExampleBase/FileUtils.cpp @@ -75,6 +75,11 @@ static std::string FindAssetFilename(const std::string& name) // Handle filename resolution with AAssetManager on Android return name; + #elif defined LLGL_OS_WASM + + // Read asset files for examples from asset folder + return "assets/" + name; + #else // Return input name if it's a valid filename diff --git a/examples/Cpp/ExampleBase/Wasm/index.html b/examples/Cpp/ExampleBase/Wasm/index.html new file mode 100644 index 0000000000..2d0c65aec7 --- /dev/null +++ b/examples/Cpp/ExampleBase/Wasm/index.html @@ -0,0 +1,48 @@ + + + + + + LLGL - LLGL_EXAMPLE_NAME + + + + +
+ +
+ + + + + + + + \ No newline at end of file diff --git a/sources/Renderer/OpenGL/GLCore.cpp b/sources/Renderer/OpenGL/GLCore.cpp index bfc6e86a7f..a0ef25902e 100644 --- a/sources/Renderer/OpenGL/GLCore.cpp +++ b/sources/Renderer/OpenGL/GLCore.cpp @@ -179,7 +179,7 @@ void ErrUnsupportedGLProc(const char* name) { #if defined(LLGL_OPENGL) LLGL_TRAP("illegal use of unsupported OpenGL procedure: %s", name); - #elif defined(LLGL_OS_EMSCRIPTEN) + #elif defined(LLGL_OS_WASM) LLGL_TRAP("illegal use of unsupported OpenGLES procedure: %s", name); #else LLGL_TRAP("illegal use of unsupported WebGL procedure: %s", name); diff --git a/sources/Renderer/OpenGL/GLCoreProfile/OpenGLCore.h b/sources/Renderer/OpenGL/GLCoreProfile/OpenGLCore.h index ac66d6fced..382f2f03a1 100644 --- a/sources/Renderer/OpenGL/GLCoreProfile/OpenGLCore.h +++ b/sources/Renderer/OpenGL/GLCoreProfile/OpenGLCore.h @@ -17,8 +17,6 @@ # include # include # include -#elif defined LLGL_OS_EMSCRIPTEN -# include #elif defined LLGL_OS_LINUX # include # include diff --git a/sources/Renderer/OpenGL/GLESProfile/GLESExtensionLoader.cpp b/sources/Renderer/OpenGL/GLESProfile/GLESExtensionLoader.cpp index eefc4e1e17..66854a5fc2 100644 --- a/sources/Renderer/OpenGL/GLESProfile/GLESExtensionLoader.cpp +++ b/sources/Renderer/OpenGL/GLESProfile/GLESExtensionLoader.cpp @@ -11,7 +11,7 @@ #include "GLESExtensionsProxy.h" #include "OpenGLES.h" #include "../GLCore.h" -#if defined(LLGL_OS_ANDROID) || defined(LLGL_OS_EMSCRIPTEN) +#if defined(LLGL_OS_ANDROID) # include #endif #include diff --git a/sources/Renderer/OpenGL/WebGLProfile/WebGLExtensionLoader.cpp b/sources/Renderer/OpenGL/WebGLProfile/WebGLExtensionLoader.cpp index 718f0c38b6..d0cdac65ed 100644 --- a/sources/Renderer/OpenGL/WebGLProfile/WebGLExtensionLoader.cpp +++ b/sources/Renderer/OpenGL/WebGLProfile/WebGLExtensionLoader.cpp @@ -9,7 +9,7 @@ #include "../Ext/GLExtensionRegistry.h" #include "WebGL.h" #include "../GLCore.h" -#if defined(LLGL_OS_EMSCRIPTEN) +#if defined(LLGL_OS_WASM) # include #endif #include From 24b31bd8fe81340d1f621c1635a6536922677f8f Mon Sep 17 00:00:00 2001 From: Laura Hermanns Date: Thu, 22 Aug 2024 12:50:37 -0400 Subject: [PATCH 10/15] [Docu] Added new UWP icon and Wasm build documentation. - Added SVG icon for UWP logo to distinguish it from Windows platform. - Added documentation how to build LLGL for Wasm. --- README.md | 2 +- docu/Icons/uwp.svg | 21 +++++++++++++++++++++ docu/Icons/uwp.svg.license.txt | 9 +++++++++ docu/README.md | 15 +++++++++++++++ 4 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 docu/Icons/uwp.svg create mode 100644 docu/Icons/uwp.svg.license.txt diff --git a/README.md b/README.md index d73c1d1be2..e3d60b3664 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ with Introduction, Hello Triangle Tutorial, and Extensibility Example with [GLFW | Platform | CI | D3D12 | D3D11 | Vulkan | GL/GLES/WebGL | Metal | |----------|:--:|:-----:|:-----:|:------:|:--------:|:-----:| | Windows |

[![MSVC16+ CI](https://github.com/LukasBanana/LLGL/actions/workflows/ci_windows.yml/badge.svg)](https://github.com/LukasBanana/LLGL/actions/workflows/ci_windows.yml)

[![MSVC14 CI](https://ci.appveyor.com/api/projects/status/j09x8n07u3byfky0?svg=true)](https://ci.appveyor.com/project/LukasBanana/llgl)

| :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | N/A | -| UWP | [![UWP CI](https://github.com/LukasBanana/LLGL/actions/workflows/ci_uwp.yml/badge.svg)](https://github.com/LukasBanana/LLGL/actions/workflows/ci_uwp.yml) | :heavy_check_mark: | :heavy_check_mark: | N/A | N/A | N/A | +| UWP | [![UWP CI](https://github.com/LukasBanana/LLGL/actions/workflows/ci_uwp.yml/badge.svg)](https://github.com/LukasBanana/LLGL/actions/workflows/ci_uwp.yml) | :heavy_check_mark: | :heavy_check_mark: | N/A | N/A | N/A | | GNU/Linux | [![GNU/Linux CI](https://github.com/LukasBanana/LLGL/actions/workflows/ci_linux.yml/badge.svg)](https://github.com/LukasBanana/LLGL/actions/workflows/ci_linux.yml) | N/A | N/A | :heavy_check_mark: | :heavy_check_mark: | N/A | | macOS | [![macOS CI](https://github.com/LukasBanana/LLGL/actions/workflows/ci_macos.yml/badge.svg)](https://github.com/LukasBanana/LLGL/actions/workflows/ci_macos.yml) | N/A | N/A | N/A | :heavy_check_mark: | :heavy_check_mark: | | iOS | [![iOS CI](https://github.com/LukasBanana/LLGL/actions/workflows/ci_ios.yml/badge.svg)](https://github.com/LukasBanana/LLGL/actions/workflows/ci_ios.yml) | N/A | N/A | N/A | :heavy_check_mark: | :heavy_check_mark: | diff --git a/docu/Icons/uwp.svg b/docu/Icons/uwp.svg new file mode 100644 index 0000000000..9005f28de6 --- /dev/null +++ b/docu/Icons/uwp.svg @@ -0,0 +1,21 @@ + + + + + + + + diff --git a/docu/Icons/uwp.svg.license.txt b/docu/Icons/uwp.svg.license.txt new file mode 100644 index 0000000000..9ab63821d7 --- /dev/null +++ b/docu/Icons/uwp.svg.license.txt @@ -0,0 +1,9 @@ +License: + CC BY 3.0 (https://creativecommons.org/licenses/by/3.0/) + +Attribution: + Icon created by [ViconsDesign] from https://www.iconfinder.com/ViconsDesign + +Weblink: + https://www.iconfinder.com/ViconsDesign + https://www.iconfinder.com/icons/7422390/microsoft_office_windows_window_icon \ No newline at end of file diff --git a/docu/README.md b/docu/README.md index 556bc3f741..4fdd31a574 100644 --- a/docu/README.md +++ b/docu/README.md @@ -63,3 +63,18 @@ $ pacman -S mingw-w64-x86_64-freeglut The build script `BuildMsys2.sh` can be used to automate the build process. Use `$ ./BuildMsys2.sh -h` in a command prompt for more details. + +## WebAssembly (Wasm) + +LLGL can be build for the web using [Emscripten](https://emscripten.org/), [WebAssembly](https://webassembly.org/), and [WebGL 2](https://www.khronos.org/webgl/). +This is in an **experimental** state and requires [Google Chrome](https://www.google.com/chrome/) to be launched with the following arguments to enable web assembly: +``` +chrome --js-flags=--experimental-wasm-threads --enable-features=WebAssembly,SharedArrayBuffer +``` +When building on Windows, it is recommended to use the [Windows Subsystem for Linux (WSL)](https://ubuntu.com/desktop/wsl) to run the *BuildWasm.sh* script. +The generated example projects can be tested by running a local web server from the output folder, for instance with [Node.js](https://nodejs.org/) or Python `http.server` module: +``` +$ python -m http.server +``` +Then run your browser of choice at URL http://localhost:8000/. + From 45ffecbf88a30e9a858a1ce63f562855091ebed8 Mon Sep 17 00:00:00 2001 From: Laura Hermanns Date: Thu, 22 Aug 2024 20:59:31 -0400 Subject: [PATCH 11/15] [Wasm] Added example showcase to documentation. --- docu/README.md | 4 ++++ docu/Showcase/Showcase_Build_Wasm.png | Bin 0 -> 79838 bytes 2 files changed, 4 insertions(+) create mode 100644 docu/Showcase/Showcase_Build_Wasm.png diff --git a/docu/README.md b/docu/README.md index 4fdd31a574..8779f87104 100644 --- a/docu/README.md +++ b/docu/README.md @@ -78,3 +78,7 @@ $ python -m http.server ``` Then run your browser of choice at URL http://localhost:8000/. +

+ Screenshot missing: HTML5 Example Showcase +

+ diff --git a/docu/Showcase/Showcase_Build_Wasm.png b/docu/Showcase/Showcase_Build_Wasm.png new file mode 100644 index 0000000000000000000000000000000000000000..6a99ee891299b948b809aedb7f515ab8fcbe73c4 GIT binary patch literal 79838 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 z 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

2G7Q6;PnR!`bTq?zmHl>U%2Z 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 Date: Thu, 22 Aug 2024 21:03:32 -0400 Subject: [PATCH 12/15] [Wasm] Continued with examples with first one running "Animation". - Implemented basic functionality of Display interface. - Hooked up Emscripten event listeners to Canvas interface. - Added asset packaging code to BuildWasm.sh script (similar to Android build script). --- BuildWasm.sh | 4 + CMakeLists.txt | 1 + examples/Cpp/ExampleBase/ExampleBase.cpp | 152 ++++++---- examples/Cpp/ExampleBase/ExampleBase.h | 9 + examples/Cpp/ExampleBase/Wasm/index.html | 3 +- include/LLGL/Platform/Wasm/WasmNativeHandle.h | 2 +- include/LLGL/RendererConfiguration.h | 5 +- sources/Platform/Wasm/WasmCanvas.cpp | 271 ++++++++---------- sources/Platform/Wasm/WasmCanvas.h | 10 +- sources/Platform/Wasm/WasmDisplay.cpp | 33 ++- sources/Renderer/OpenGL/GLSwapChain.cpp | 2 +- .../OpenGL/Platform/Wasm/WasmGLContext.cpp | 47 ++- .../Platform/Wasm/WasmGLSwapChainContext.cpp | 26 +- .../WebGLProfile/WebGLExtensionLoader.cpp | 22 +- 14 files changed, 311 insertions(+), 276 deletions(-) diff --git a/BuildWasm.sh b/BuildWasm.sh index eda252b62f..f38738c5ba 100644 --- a/BuildWasm.sh +++ b/BuildWasm.sh @@ -102,6 +102,7 @@ if [ -z "$EMSDK" ]; then fi EMSCRIPTEN_CMAKE_TOOLCHAIN="$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake" +EMSCRIPTEN_FILE_PACKAGER="$EMSDK/upstream/emscripten/tools/file_packager" if [ ! -f "$EMSCRIPTEN_CMAKE_TOOLCHAIN" ]; then echo "Error: Could not find file $EMSCRIPTEN_CMAKE_TOOLCHAIN" @@ -198,6 +199,9 @@ generate_html5_page() cp "$FILE" "$ASSET_DIR/$(basename $FILE)" fi done + + # Package assets into .data.js file with Emscripten packager tool + (cd "$HTML5_ROOT" && "$EMSCRIPTEN_FILE_PACKAGER" "$BASE_FILENAME.data" --preload assets "--js-output=$BASE_FILENAME.data.js" >/dev/null 2>&1) } if [ $PROJECT_ONLY -eq 0 ] && [ $ENABLE_EXAMPLES == "ON" ]; then diff --git a/CMakeLists.txt b/CMakeLists.txt index a42f465215..92874e31b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -492,6 +492,7 @@ endif() if(EMSCRIPTEN) add_compile_options("SHELL:-s USE_PTHREADS") add_link_options("SHELL:-s USE_PTHREADS") + add_link_options("SHELL:-s FORCE_FILESYSTEM") # for examples # LLGL needs at least WebGL 2.0 add_link_options("SHELL:-s MIN_WEBGL_VERSION=2") diff --git a/examples/Cpp/ExampleBase/ExampleBase.cpp b/examples/Cpp/ExampleBase/ExampleBase.cpp index e6f976ff12..36af10d139 100644 --- a/examples/Cpp/ExampleBase/ExampleBase.cpp +++ b/examples/Cpp/ExampleBase/ExampleBase.cpp @@ -28,9 +28,13 @@ #endif #include -#ifdef LLGL_OS_ANDROID +#if defined LLGL_OS_ANDROID # include "Android/AppUtils.h" # include +#elif defined LLGL_OS_WASM +# include +# include +# include #endif #define IMMEDIATE_SUBMIT_CMDBUFFER 0 @@ -377,16 +381,27 @@ void ExampleBase::SetAndroidApp(android_app* androidApp) #endif +void ExampleBase::MainLoopWrapper(void* args) +{ + ExampleBase* exampleBase = reinterpret_cast(args); + exampleBase->MainLoop(); +} + void ExampleBase::Run() { - bool showTimeRecords = false; - bool fullscreen = false; - const LLGL::Extent2D initialResolution = swapChain->GetResolution(); + initialResolution_ = swapChain->GetResolution(); #ifndef LLGL_MOBILE_PLATFORM LLGL::Window& window = LLGL::CastTo(swapChain->GetSurface()); #endif + #ifdef LLGL_OS_WASM + + // Receives a function to call and some user data to provide it. + emscripten_set_main_loop_arg(ExampleBase::MainLoopWrapper, this, 0, 1); + + #else + while (LLGL::Surface::ProcessEvents() && !input.KeyDown(LLGL::Key::Escape)) { #ifndef LLGL_MOBILE_PLATFORM @@ -407,57 +422,10 @@ void ExampleBase::Run() ANativeActivity_finish(ExampleBase::androidApp_->activity); #endif - // Update profiler (if debugging is enabled) - if (debuggerObj_) - { - LLGL::FrameProfile frameProfile; - debuggerObj_->FlushProfile(&frameProfile); - - if (showTimeRecords) - { - LLGL::Log::Printf( - "\n" - "FRAME TIME RECORDS:\n" - "-------------------\n" - ); - for (const LLGL::ProfileTimeRecord& rec : frameProfile.timeRecords) - LLGL::Log::Printf("%s: %" PRIu64 " ns\n", rec.annotation, rec.elapsedTime); - - debuggerObj_->SetTimeRecording(false); - showTimeRecords = false; - } - else if (input.KeyDown(LLGL::Key::F1)) - { - debuggerObj_->SetTimeRecording(true); - showTimeRecords = true; - } - } - - // Check to switch to fullscreen - if (input.KeyDown(LLGL::Key::F5)) - { - if (LLGL::Display* display = swapChain->GetSurface().FindResidentDisplay()) - { - fullscreen = !fullscreen; - if (fullscreen) - swapChain->ResizeBuffers(display->GetDisplayMode().resolution, LLGL::ResizeBuffersFlags::FullscreenMode); - else - swapChain->ResizeBuffers(initialResolution, LLGL::ResizeBuffersFlags::WindowedMode); - } - } - - // Draw current frame - #ifdef LLGL_OS_MACOS - @autoreleasepool - { - DrawFrame(); - } - #else - DrawFrame(); - #endif - - input.Reset(); + MainLoop(); } + + #endif // /LLGL_OS_WASM } void ExampleBase::DrawFrame() @@ -654,6 +622,74 @@ void ExampleBase::OnResize(const LLGL::Extent2D& resolution) // dummy } +void ExampleBase::MainLoop() +{ + #ifdef LLGL_OS_WASM + + // Clear GL state when rendering with WebGL + commands->Begin(); + { + LLGL::OpenGL::NativeCommand cmd; + cmd.type = LLGL::OpenGL::NativeCommandType::ClearCache; + commands->DoNativeCommand(&cmd, sizeof(cmd)); + } + commands->End(); + commandQueue->Submit(*commands); + + #endif + + // Update profiler (if debugging is enabled) + if (debuggerObj_) + { + LLGL::FrameProfile frameProfile; + debuggerObj_->FlushProfile(&frameProfile); + + if (showTimeRecords_) + { + LLGL::Log::Printf( + "\n" + "FRAME TIME RECORDS:\n" + "-------------------\n" + ); + for (const LLGL::ProfileTimeRecord& rec : frameProfile.timeRecords) + LLGL::Log::Printf("%s: %" PRIu64 " ns\n", rec.annotation, rec.elapsedTime); + + debuggerObj_->SetTimeRecording(false); + showTimeRecords_ = false; + } + else if (input.KeyDown(LLGL::Key::F1)) + { + debuggerObj_->SetTimeRecording(true); + showTimeRecords_ = true; + } + } + + // Check to switch to fullscreen + if (input.KeyDown(LLGL::Key::F5)) + { + if (LLGL::Display* display = swapChain->GetSurface().FindResidentDisplay()) + { + fullscreen_ = !fullscreen_; + if (fullscreen_) + swapChain->ResizeBuffers(display->GetDisplayMode().resolution, LLGL::ResizeBuffersFlags::FullscreenMode); + else + swapChain->ResizeBuffers(initialResolution_, LLGL::ResizeBuffersFlags::WindowedMode); + } + } + + // Draw current frame + #ifdef LLGL_OS_MACOS + @autoreleasepool + { + DrawFrame(); + } + #else + DrawFrame(); + #endif + + input.Reset(); +} + //private LLGL::Shader* ExampleBase::LoadShaderInternal( const ShaderDescWrapper& shaderDesc, @@ -665,6 +701,12 @@ LLGL::Shader* ExampleBase::LoadShaderInternal( { LLGL::Log::Printf("load shader: %s\n", shaderDesc.filename.c_str()); + #ifdef LLGL_OS_WASM + const std::string filename = "assets/" + shaderDesc.filename; + #else + const std::string filename = shaderDesc.filename; + #endif + std::vector shaders; std::vector vertexInputAttribs; @@ -679,7 +721,7 @@ LLGL::Shader* ExampleBase::LoadShaderInternal( } // Create shader - LLGL::ShaderDescriptor deviceShaderDesc = LLGL::ShaderDescFromFile(shaderDesc.type, shaderDesc.filename.c_str(), shaderDesc.entryPoint.c_str(), shaderDesc.profile.c_str()); + LLGL::ShaderDescriptor deviceShaderDesc = LLGL::ShaderDescFromFile(shaderDesc.type, filename.c_str(), shaderDesc.entryPoint.c_str(), shaderDesc.profile.c_str()); { deviceShaderDesc.debugName = shaderDesc.entryPoint.c_str(); diff --git a/examples/Cpp/ExampleBase/ExampleBase.h b/examples/Cpp/ExampleBase/ExampleBase.h index 432198f271..7b9abcafcb 100644 --- a/examples/Cpp/ExampleBase/ExampleBase.h +++ b/examples/Cpp/ExampleBase/ExampleBase.h @@ -156,6 +156,10 @@ class ExampleBase std::uint32_t samples_ = 1; + LLGL::Extent2D initialResolution_; + bool showTimeRecords_ = false; + bool fullscreen_ = false; + protected: friend class ResizeEventHandler; @@ -196,6 +200,11 @@ class ExampleBase private: + static void MainLoopWrapper(void* args); + + // Internal main loop. This is called manually on most platforms. With WebAssembly, it's passed to the browser glue code. + void MainLoop(); + // Internal function to load a shader. LLGL::Shader* LoadShaderInternal( const ShaderDescWrapper& shaderDesc, diff --git a/examples/Cpp/ExampleBase/Wasm/index.html b/examples/Cpp/ExampleBase/Wasm/index.html index 2d0c65aec7..b0ebcd828e 100644 --- a/examples/Cpp/ExampleBase/Wasm/index.html +++ b/examples/Cpp/ExampleBase/Wasm/index.html @@ -43,6 +43,7 @@ - + + \ No newline at end of file diff --git a/include/LLGL/Platform/Wasm/WasmNativeHandle.h b/include/LLGL/Platform/Wasm/WasmNativeHandle.h index e09d5f3bd9..2e784a83cd 100644 --- a/include/LLGL/Platform/Wasm/WasmNativeHandle.h +++ b/include/LLGL/Platform/Wasm/WasmNativeHandle.h @@ -20,7 +20,7 @@ namespace LLGL struct NativeHandle { //! CSS selector of canvas object. - std::string canvas; + emscripten::val canvas; }; diff --git a/include/LLGL/RendererConfiguration.h b/include/LLGL/RendererConfiguration.h index 0290d0dbae..9f1f08381e 100644 --- a/include/LLGL/RendererConfiguration.h +++ b/include/LLGL/RendererConfiguration.h @@ -35,7 +35,8 @@ enum class OpenGLContextProfile /** \brief OpenGL ES profile. - \note Only supported on: Android and iOS. + \remarks This profile is used for both OpenGL ES and WebGL since WebGL shaders also refer to the ES profile. + \note Only supported on: Android, iOS, and WebAssembly. */ ESProfile, @@ -43,7 +44,7 @@ enum class OpenGLContextProfile \brief Default GL profile. \remarks This is equivalent to CoreProfile for OpenGL and ESProfile for OpenGLES. */ - #if defined(LLGL_OS_ANDROID) || defined(LLGL_OS_IOS) + #if defined(LLGL_OS_ANDROID) || defined(LLGL_OS_IOS) || defined(LLGL_OS_WASM) DefaultProfile = ESProfile, #else DefaultProfile = CoreProfile, diff --git a/sources/Platform/Wasm/WasmCanvas.cpp b/sources/Platform/Wasm/WasmCanvas.cpp index 002d0de005..419db79ca9 100644 --- a/sources/Platform/Wasm/WasmCanvas.cpp +++ b/sources/Platform/Wasm/WasmCanvas.cpp @@ -31,20 +31,6 @@ bool Surface::ProcessEvents() * Canvas class */ -static Offset2D GetScreenCenteredPosition(const Extent2D& size) -{ - if (auto display = Display::GetPrimary()) - { - const auto resolution = display->GetDisplayMode().resolution; - return - { - static_cast((resolution.width - size.width )/2), - static_cast((resolution.height - size.height)/2), - }; - } - return {}; -} - std::unique_ptr Canvas::Create(const CanvasDescriptor& desc) { return MakeUnique(desc); @@ -65,7 +51,7 @@ bool WasmCanvas::GetNativeHandle(void* nativeHandle, std::size_t nativeHandleSiz if (nativeHandle != nullptr && nativeHandleSize == sizeof(NativeHandle)) { auto* handle = reinterpret_cast(nativeHandle); - //handle->visual = visual_; + handle->canvas = canvas_; return true; } return false; @@ -84,13 +70,12 @@ Extent2D WasmCanvas::GetContentSize() const void WasmCanvas::SetTitle(const UTF8String& title) { - //todo + emscripten_set_window_title(title.c_str()); } UTF8String WasmCanvas::GetTitle() const { - char* title = nullptr; //todo - return title; + return emscripten_get_window_title(); } @@ -141,16 +126,16 @@ static const char* EmscriptenResultToString(EMSCRIPTEN_RESULT result) return "Unknown EMSCRIPTEN_RESULT!"; } -static int interpret_charcode_for_keyevent(int eventType, const EmscriptenKeyboardEvent *e) +static int interpret_charcode_for_keyevent(int eventType, const EmscriptenKeyboardEvent* event) { // Only KeyPress events carry a charCode. For KeyDown and KeyUp events, these don't seem to be present yet, until later when the KeyDown // is transformed to KeyPress. Sometimes it can be useful to already at KeyDown time to know what the charCode of the resulting // KeyPress will be. The following attempts to do this: - if (eventType == EMSCRIPTEN_EVENT_KEYPRESS && e->which) return e->which; - if (e->charCode) return e->charCode; - if (strlen(e->key) == 1) return (int)e->key[0]; - if (e->which) return e->which; - return e->keyCode; + if (eventType == EMSCRIPTEN_EVENT_KEYPRESS && event->which) return event->which; + if (event->charCode) return event->charCode; + if (strlen(event->key) == 1) return (int)event->key[0]; + if (event->which) return event->which; + return event->keyCode; } const char* WasmCanvas::OnBeforeUnloadCallback(int eventType, const void* reserved, void* userData) @@ -160,92 +145,143 @@ const char* WasmCanvas::OnBeforeUnloadCallback(int eventType, const void* reserv return nullptr; } -int WasmCanvas::OnCanvasResizeCallback(int eventType, const EmscriptenUiEvent* event, void *userData) +EM_BOOL WasmCanvas::OnCanvasResizeCallback(int eventType, const EmscriptenUiEvent* event, void* userData) { - WasmCanvas* canvas = reinterpret_cast(userData); - const Extent2D clientAreaSize + if (eventType == EMSCRIPTEN_EVENT_RESIZE) { - static_cast(event->documentBodyClientWidth), - static_cast(event->documentBodyClientHeight) - }; - canvas->PostResize(clientAreaSize); - return 0; + /* Resize this canvas as the event comes from the window and we must update the canvas resolution */ + emscripten_set_canvas_element_size("#canvas", event->windowInnerWidth, event->windowInnerHeight); + + /* Send resize event to event listeners */ + WasmCanvas* canvas = reinterpret_cast(userData); + const Extent2D clientAreaSize + { + static_cast(event->windowInnerWidth), + static_cast(event->windowInnerHeight) + }; + canvas->PostResize(clientAreaSize); + return EM_TRUE; + } + return EM_FALSE; } -EM_BOOL WasmCanvas::OnKeyCallback(int eventType, const EmscriptenKeyboardEvent *e, void *userData) +EM_BOOL WasmCanvas::OnKeyCallback(int eventType, const EmscriptenKeyboardEvent* event, void* userData) { WasmCanvas* canvas = reinterpret_cast(userData); - int dom_pk_code = emscripten_compute_dom_pk_code(e->code); + int dom_pk_code = emscripten_compute_dom_pk_code(event->code); /* printf("%s, key: \"%s\", code: \"%s\" = %s (%d), location: %lu,%s%s%s%s repeat: %d, locale: \"%s\", char: \"%s\", charCode: %lu (interpreted: %d), keyCode: %s(%lu), which: %lu\n", - emscripten_event_type_to_string(eventType), e->key, e->code, emscripten_dom_pk_code_to_string(dom_pk_code), dom_pk_code, e->location, - e->ctrlKey ? " CTRL" : "", e->shiftKey ? " SHIFT" : "", e->altKey ? " ALT" : "", e->metaKey ? " META" : "", - e->repeat, e->locale, e->charValue, e->charCode, interpret_charcode_for_keyevent(eventType, e), emscripten_dom_vk_to_string(e->keyCode), e->keyCode, e->which); + emscripten_event_type_to_string(eventType), event->key, event->code, emscripten_dom_pk_code_to_string(dom_pk_code), dom_pk_code, event->location, + event->ctrlKey ? " CTRL" : "", event->shiftKey ? " SHIFT" : "", event->altKey ? " ALT" : "", event->metaKey ? " META" : "", + event->repeat, event->locale, event->charValue, event->charCode, interpret_charcode_for_keyevent(eventType, e), emscripten_dom_vk_to_string(event->keyCode), event->keyCode, event->which); if (eventType == EMSCRIPTEN_EVENT_KEYUP) printf("\n"); // Visual cue */ - printf("%s, key: \"%s\", code: \"%s\" = %s (%d)\n", emscripten_event_type_to_string(eventType), e->key, e->code, emscripten_dom_pk_code_to_string(dom_pk_code), dom_pk_code); + printf("%s, key: \"%s\", code: \"%s\" = %s (%d)\n", emscripten_event_type_to_string(eventType), event->key, event->code, emscripten_dom_pk_code_to_string(dom_pk_code), dom_pk_code); - auto key = MapEmscriptenKeyCode(e->code); + auto key = MapEmscriptenKeyCode(event->code); if (eventType == 2) canvas->PostKeyDown(key); else if (eventType == 3) canvas->PostKeyUp(key); - return true; + return EM_TRUE; } -EM_BOOL WasmCanvas::OnMouseCallback(int eventType, const EmscriptenMouseEvent *e, void *userData) +static Key EmscriptenMouseButtonToKeyCode(unsigned short button) { - /* - printf("%s, screen: (%ld,%ld), client: (%ld,%ld),%s%s%s%s button: %hu, buttons: %hu, movement: (%ld,%ld), canvas: (%ld,%ld), target: (%ld, %ld)\n", - emscripten_event_type_to_string(eventType), e->screenX, e->screenY, e->clientX, e->clientY, - e->ctrlKey ? " CTRL" : "", e->shiftKey ? " SHIFT" : "", e->altKey ? " ALT" : "", e->metaKey ? " META" : "", - e->button, e->buttons, e->movementX, e->movementY, e->canvasX, e->canvasY, e->targetX, e->targetY); - */ + switch (button) + { + case 0: return Key::LButton; + case 1: return Key::MButton; + case 2: return Key::RButton; + default: return Key::Any; + } +} - /* - if (e->screenX != 0 && e->screenY != 0 && e->clientX != 0 && e->clientY != 0 && e->canvasX != 0 && e->canvasY != 0 && e->targetX != 0 && e->targetY != 0) +static EventAction EmscriptenMouseEventToAction(int eventType) +{ + switch (eventType) { - if (eventType == EMSCRIPTEN_EVENT_CLICK) gotClick = 1; - if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN && e->buttons != 0) gotMouseDown = 1; - if (eventType == EMSCRIPTEN_EVENT_DBLCLICK) gotDblClick = 1; - if (eventType == EMSCRIPTEN_EVENT_MOUSEUP) gotMouseUp = 1; - if (eventType == EMSCRIPTEN_EVENT_MOUSEMOVE && (e->movementX != 0 || e->movementY != 0)) gotMouseMove = 1; + case EMSCRIPTEN_EVENT_MOUSEENTER: return EventAction::Began; + case EMSCRIPTEN_EVENT_MOUSEMOVE: return EventAction::Changed; + case EMSCRIPTEN_EVENT_MOUSELEAVE: return EventAction::Ended; + default: return EventAction::Ended; // fallback } - */ +} - /* - if (eventType == EMSCRIPTEN_EVENT_CLICK && e->screenX == -500000) +EM_BOOL WasmCanvas::OnMouseCallback(int eventType, const EmscriptenMouseEvent* event, void* userData) +{ + WasmCanvas* canvas = reinterpret_cast(userData); + + switch (eventType) { - printf("ERROR! Received an event to a callback that should have been unregistered!\n"); - gotClick = 0; + case EMSCRIPTEN_EVENT_MOUSEDOWN: + { + canvas->PostKeyDown(EmscriptenMouseButtonToKeyCode(event->button)); + } + return EM_TRUE; + + case EMSCRIPTEN_EVENT_MOUSEUP: + { + canvas->PostKeyUp(EmscriptenMouseButtonToKeyCode(event->button)); + } + return EM_TRUE; + + case EMSCRIPTEN_EVENT_CLICK: + case EMSCRIPTEN_EVENT_DBLCLICK: + { + const Offset2D position + { + static_cast(event->clientX), + static_cast(event->clientY) + }; + canvas->PostTapGesture(position, 1); + } + return EM_TRUE; + + case EMSCRIPTEN_EVENT_MOUSEENTER: + case EMSCRIPTEN_EVENT_MOUSEMOVE: + case EMSCRIPTEN_EVENT_MOUSELEAVE: + { + const Offset2D position + { + static_cast(event->clientX), + static_cast(event->clientY) + }; + const float motionX = static_cast(event->movementX); + const float motionY = static_cast(event->movementY); + canvas->PostPanGesture(position, 1, motionX, motionY, EmscriptenMouseEventToAction(eventType)); + } + return EM_TRUE; + + default: + break; } - */ - return EMSCRIPTEN_RESULT_SUCCESS; + return EM_FALSE; } -EM_BOOL WasmCanvas::OnWheelCallback(int eventType, const EmscriptenWheelEvent *e, void *userData) +EM_BOOL WasmCanvas::OnWheelCallback(int eventType, const EmscriptenWheelEvent* event, void* userData) { /* printf("%s, screen: (%ld,%ld), client: (%ld,%ld),%s%s%s%s button: %hu, buttons: %hu, canvas: (%ld,%ld), target: (%ld, %ld), delta:(%g,%g,%g), deltaMode:%lu\n", - emscripten_event_type_to_string(eventType), e->mouse.screenX, e->mouse.screenY, e->mouse.clientX, e->mouse.clientY, - e->mouse.ctrlKey ? " CTRL" : "", e->mouse.shiftKey ? " SHIFT" : "", e->mouse.altKey ? " ALT" : "", e->mouse.metaKey ? " META" : "", - e->mouse.button, e->mouse.buttons, e->mouse.canvasX, e->mouse.canvasY, e->mouse.targetX, e->mouse.targetY, - (float)e->deltaX, (float)e->deltaY, (float)e->deltaZ, e->deltaMode); + emscripten_event_type_to_string(eventType), event->mouse.screenX, event->mouse.screenY, event->mouse.clientX, event->mouse.clientY, + event->mouse.ctrlKey ? " CTRL" : "", event->mouse.shiftKey ? " SHIFT" : "", event->mouse.altKey ? " ALT" : "", event->mouse.metaKey ? " META" : "", + event->mouse.button, event->mouse.buttons, event->mouse.canvasX, event->mouse.canvasY, event->mouse.targetX, event->mouse.targetY, + (float)event->deltaX, (float)event->deltaY, (float)event->deltaZ, event->deltaMode); */ /* - if (e->deltaY > 0.f || e->deltaY < 0.f) + if (event->deltaY > 0.f || event->deltaY < 0.f) gotWheel = 1; */ - return EMSCRIPTEN_RESULT_SUCCESS; + return EM_TRUE; } void WasmCanvas::CreateEmscriptenCanvas(const CanvasDescriptor& desc) @@ -254,15 +290,14 @@ void WasmCanvas::CreateEmscriptenCanvas(const CanvasDescriptor& desc) emscripten::val config = emscripten::val::module_property("config"); emscripten::val document = emscripten::val::global("document"); - if (config.isUndefined() || config.isNull()) - return; + //if (config.isUndefined() || config.isNull()) + // return; - - if (!config.hasOwnProperty("canvas_selector")) - return; + //if (!config.hasOwnProperty("canvas_selector")) + // return; - std::string canvas_selector = config["canvas_selector"].as(); - canvas_ = document["body"].call("querySelector", canvas_selector); + std::string canvasSelector = "#canvas";//config["canvas_selector"].as(); + canvas_ = document["body"].call("querySelector", canvasSelector); EM_ASM({ console.log(Emval.toValue($0)); @@ -277,81 +312,17 @@ void WasmCanvas::CreateEmscriptenCanvas(const CanvasDescriptor& desc) /* Set callbacks */ EMSCRIPTEN_RESULT ret; ret = emscripten_set_beforeunload_callback(this, WasmCanvas::OnBeforeUnloadCallback); - ret = emscripten_set_resize_callback(canvas_selector.c_str(), this, true, WasmCanvas::OnCanvasResizeCallback); - - ret = emscripten_set_keydown_callback(canvas_selector.c_str(), this, true, WasmCanvas::OnKeyCallback); - ret = emscripten_set_keyup_callback(canvas_selector.c_str(), this, true, WasmCanvas::OnKeyCallback); - - ret = emscripten_set_click_callback(canvas_selector.c_str(), this, true, WasmCanvas::OnMouseCallback); - ret = emscripten_set_mousedown_callback(canvas_selector.c_str(), this, true, WasmCanvas::OnMouseCallback); - ret = emscripten_set_mouseup_callback(canvas_selector.c_str(), this, true, WasmCanvas::OnMouseCallback); - ret = emscripten_set_dblclick_callback(canvas_selector.c_str(), this, true, WasmCanvas::OnMouseCallback); - ret = emscripten_set_mousemove_callback(canvas_selector.c_str(), this, true, WasmCanvas::OnMouseCallback); - ret = emscripten_set_wheel_callback(canvas_selector.c_str(), this, true, WasmCanvas::OnWheelCallback); -} - -void WasmCanvas::ProcessKeyEvent(/*event, bool down*/) -{ - /*auto key = MapKey(event); - if (down) - PostKeyDown(key); - else - PostKeyUp(key);*/ -} - -void WasmCanvas::ProcessMouseKeyEvent(/*event, bool down*/) -{ - /*switch (event.button) - { - case Button1: - PostMouseKeyEvent(Key::LButton, down); - break; - case Button2: - PostMouseKeyEvent(Key::MButton, down); - break; - case Button3: - PostMouseKeyEvent(Key::RButton, down); - break; - case Button4: - PostWheelMotion(1); - break; - case Button5: - PostWheelMotion(-1); - break; - }*/ -} - -/*void WasmCanvas::ProcessExposeEvent() -{ - const Extent2D size - { - static_cast(0), - static_cast(0) - }; - PostResize(size); -}*/ - -void WasmCanvas::ProcessClientMessage(/*XClientMessageEvent& event*/) -{ - /*Atom atom = static_cast(event.data.l[0]); - if (atom == closeWndAtom_) - PostQuit();*/ -} - -void WasmCanvas::ProcessMotionEvent(/*XMotionEvent& event*/) -{ - /*const Offset2D mousePos { event.x, event.y }; - PostLocalMotion(mousePos); - PostGlobalMotion({ mousePos.x - prevMousePos_.x, mousePos.y - prevMousePos_.y }); - prevMousePos_ = mousePos;*/ -} - -void WasmCanvas::PostMouseKeyEvent(Key key, bool down) -{ - if (down) - PostKeyDown(key); - else - PostKeyUp(key); + ret = emscripten_set_resize_callback (EMSCRIPTEN_EVENT_TARGET_WINDOW, this, EM_TRUE, WasmCanvas::OnCanvasResizeCallback); + ret = emscripten_set_keydown_callback (/*canvasSelector.c_str()*/EMSCRIPTEN_EVENT_TARGET_WINDOW, this, EM_TRUE, WasmCanvas::OnKeyCallback); + ret = emscripten_set_keyup_callback (/*canvasSelector.c_str()*/EMSCRIPTEN_EVENT_TARGET_WINDOW, this, EM_TRUE, WasmCanvas::OnKeyCallback); + ret = emscripten_set_click_callback (/*canvasSelector.c_str()*/EMSCRIPTEN_EVENT_TARGET_WINDOW, this, EM_TRUE, WasmCanvas::OnMouseCallback); + ret = emscripten_set_dblclick_callback (/*canvasSelector.c_str()*/EMSCRIPTEN_EVENT_TARGET_WINDOW, this, EM_TRUE, WasmCanvas::OnMouseCallback); + ret = emscripten_set_mousedown_callback (/*canvasSelector.c_str()*/EMSCRIPTEN_EVENT_TARGET_WINDOW, this, EM_TRUE, WasmCanvas::OnMouseCallback); + ret = emscripten_set_mouseup_callback (/*canvasSelector.c_str()*/EMSCRIPTEN_EVENT_TARGET_WINDOW, this, EM_TRUE, WasmCanvas::OnMouseCallback); + ret = emscripten_set_mousemove_callback (/*canvasSelector.c_str()*/EMSCRIPTEN_EVENT_TARGET_WINDOW, this, EM_TRUE, WasmCanvas::OnMouseCallback); + ret = emscripten_set_mouseenter_callback(/*canvasSelector.c_str()*/EMSCRIPTEN_EVENT_TARGET_WINDOW, this, EM_TRUE, WasmCanvas::OnMouseCallback); + ret = emscripten_set_mouseleave_callback(/*canvasSelector.c_str()*/EMSCRIPTEN_EVENT_TARGET_WINDOW, this, EM_TRUE, WasmCanvas::OnMouseCallback); + ret = emscripten_set_wheel_callback (/*canvasSelector.c_str()*/EMSCRIPTEN_EVENT_TARGET_WINDOW, this, EM_TRUE, WasmCanvas::OnWheelCallback); } diff --git a/sources/Platform/Wasm/WasmCanvas.h b/sources/Platform/Wasm/WasmCanvas.h index 167a3d16b8..c8a4ebe28a 100644 --- a/sources/Platform/Wasm/WasmCanvas.h +++ b/sources/Platform/Wasm/WasmCanvas.h @@ -38,19 +38,11 @@ class WasmCanvas : public Canvas private: void CreateEmscriptenCanvas(const CanvasDescriptor& desc); - - void ProcessKeyEvent(/*event, bool down*/); - void ProcessMouseKeyEvent(/*event, bool down*/); - //void ProcessExposeEvent(); - void ProcessClientMessage(/*event*/); - void ProcessMotionEvent(/*event*/); - - void PostMouseKeyEvent(Key key, bool down); private: static const char* OnBeforeUnloadCallback(int eventType, const void* reserved, void* userData); - static int OnCanvasResizeCallback(int eventType, const EmscriptenUiEvent *keyEvent, void *userData); + static EM_BOOL OnCanvasResizeCallback(int eventType, const EmscriptenUiEvent *keyEvent, void *userData); static EM_BOOL OnKeyCallback(int eventType, const EmscriptenKeyboardEvent *e, void *userData); diff --git a/sources/Platform/Wasm/WasmDisplay.cpp b/sources/Platform/Wasm/WasmDisplay.cpp index 96d0c1f665..903d18a6d0 100644 --- a/sources/Platform/Wasm/WasmDisplay.cpp +++ b/sources/Platform/Wasm/WasmDisplay.cpp @@ -7,6 +7,7 @@ #include "WasmDisplay.h" #include "../../Core/CoreUtils.h" +#include namespace LLGL @@ -24,20 +25,22 @@ std::size_t Display::Count() Display* const * Display::GetList() { - return nullptr; + static Display* const displayList[] = { Display::GetPrimary(), nullptr }; + return displayList; } Display* Display::Get(std::size_t index) { - return nullptr; + return (index == 0 ? Display::GetPrimary() : nullptr); } Display* Display::GetPrimary() { - return nullptr; + static WasmDisplay primary; + return &primary; } -bool Display::ShowCursor(bool show) +bool Display::ShowCursor(bool /*show*/) { return false; } @@ -47,9 +50,9 @@ bool Display::IsCursorShown() return true; } -bool Display::SetCursorPosition(const Offset2D& position) +bool Display::SetCursorPosition(const Offset2D& /*position*/) { - return true; + return false; // dummy } Offset2D Display::GetCursorPosition() @@ -86,26 +89,30 @@ float WasmDisplay::GetScale() const bool WasmDisplay::ResetDisplayMode() { - return false; + return false; // dummy } -bool WasmDisplay::SetDisplayMode(const DisplayMode& displayMode) +bool WasmDisplay::SetDisplayMode(const DisplayMode& /*displayMode*/) { - return false; + return false; // dummy } DisplayMode WasmDisplay::GetDisplayMode() const { DisplayMode displayMode; - + { + int width = 0, height = 0; + emscripten_get_screen_size(&width, &height); + displayMode.resolution.width = static_cast(width); + displayMode.resolution.height = static_cast(height); + displayMode.refreshRate = 60; // default to 60 Hz + } return displayMode; } std::vector WasmDisplay::GetSupportedDisplayModes() const { - std::vector displayModes; - DisplayMode displayMode; // todo - return displayModes; + return { GetDisplayMode() }; } diff --git a/sources/Renderer/OpenGL/GLSwapChain.cpp b/sources/Renderer/OpenGL/GLSwapChain.cpp index 4aebe50ae1..f56eb43ad8 100644 --- a/sources/Renderer/OpenGL/GLSwapChain.cpp +++ b/sources/Renderer/OpenGL/GLSwapChain.cpp @@ -30,7 +30,7 @@ namespace LLGL static GLint GetFramebufferHeight(const Extent2D& resolution) { - #ifndef LLGL_OPENGL + #if !defined(LLGL_OPENGL) && !defined(LLGL_OS_WASM) /* For GLES, the framebuffer height is determined by the display height */ if (LLGL::Display* display = LLGL::Display::GetPrimary()) { diff --git a/sources/Renderer/OpenGL/Platform/Wasm/WasmGLContext.cpp b/sources/Renderer/OpenGL/Platform/Wasm/WasmGLContext.cpp index 3d2621104e..bfbdb81efd 100644 --- a/sources/Renderer/OpenGL/Platform/Wasm/WasmGLContext.cpp +++ b/sources/Renderer/OpenGL/Platform/Wasm/WasmGLContext.cpp @@ -76,28 +76,45 @@ bool WasmGLContext::GetNativeHandle(void* nativeHandle, std::size_t nativeHandle * ======= Private: ======= */ -bool WasmGLContext::SetSwapInterval(int interval) +bool WasmGLContext::SetSwapInterval(int /*interval*/) { - return true; + return false; // dummy +} + +static void GetWebGLVersionFromConfig(EmscriptenWebGLContextAttributes& attrs, const RendererConfigurationOpenGL& cfg) +{ + if (cfg.majorVersion == 0 && cfg.minorVersion == 0) + { + /* WebGL 2.0 is requested by default */ + attrs.majorVersion = 2; + attrs.minorVersion = 0; + } + else + { + /* Request custom WebGL version (can only be 1.0 or 2.0) */ + attrs.majorVersion = cfg.majorVersion; + attrs.minorVersion = cfg.minorVersion; + } } void WasmGLContext::CreateContext(const GLPixelFormat& pixelFormat, const RendererConfigurationOpenGL& profile, WasmGLContext* sharedContext) { - EmscriptenWebGLContextAttributes attrs; + EmscriptenWebGLContextAttributes attrs = {}; emscripten_webgl_init_context_attributes(&attrs); - attrs.majorVersion = 2; - attrs.minorVersion = 0; - attrs.alpha = (pixelFormat.colorBits > 24); - attrs.depth = (pixelFormat.depthBits > 0); - attrs.stencil = (pixelFormat.stencilBits > 0); - attrs.antialias = (pixelFormat.samples > 1); - attrs.premultipliedAlpha = true; - attrs.preserveDrawingBuffer = false; - attrs.explicitSwapControl = 0; - attrs.failIfMajorPerformanceCaveat = false; - attrs.enableExtensionsByDefault = true; - attrs.powerPreference = EM_WEBGL_POWER_PREFERENCE_DEFAULT; + GetWebGLVersionFromConfig(attrs, profile); + attrs.alpha = true;//(pixelFormat.colorBits > 24); + attrs.depth = true;//(pixelFormat.depthBits > 0); + attrs.stencil = true;//(pixelFormat.stencilBits > 0); + attrs.antialias = true;//(pixelFormat.samples > 1); + attrs.premultipliedAlpha = true; + attrs.preserveDrawingBuffer = false; + attrs.explicitSwapControl = 0; + attrs.failIfMajorPerformanceCaveat = false; + attrs.enableExtensionsByDefault = true; + attrs.powerPreference = EM_WEBGL_POWER_PREFERENCE_DEFAULT; + + //TODO: determine canvas ID context_ = emscripten_webgl_create_context("#canvas", &attrs); if (!context_) diff --git a/sources/Renderer/OpenGL/Platform/Wasm/WasmGLSwapChainContext.cpp b/sources/Renderer/OpenGL/Platform/Wasm/WasmGLSwapChainContext.cpp index 25a74b5b6e..759b2c320a 100644 --- a/sources/Renderer/OpenGL/Platform/Wasm/WasmGLSwapChainContext.cpp +++ b/sources/Renderer/OpenGL/Platform/Wasm/WasmGLSwapChainContext.cpp @@ -7,7 +7,7 @@ #include "WasmGLSwapChainContext.h" #include "../../../../Core/CoreUtils.h" -#include "../../../../Core/Exception.h" +#include "../../../../Core/Assertion.h" #include @@ -69,25 +69,15 @@ void WasmGLSwapChainContext::Resize(const Extent2D& resolution) bool WasmGLSwapChainContext::MakeCurrentEGLContext(WasmGLSwapChainContext* context) { - return true; EMSCRIPTEN_RESULT res = emscripten_webgl_make_context_current(context->context_); + LLGL_ASSERT(res == EMSCRIPTEN_RESULT_SUCCESS, "emscripten_webgl_make_context_current() failed"); + + //LLGL_ASSERT(emscripten_webgl_get_current_context() == context->GetGLContext()); - if (res == EMSCRIPTEN_RESULT_SUCCESS) - { - //assert(emscripten_webgl_get_current_context() == context->GetGLContext()); - - int width, height, fs = 0; - emscripten_get_canvas_element_size("#mycanvas", &width, &height); - printf("width:%d, height:%d\n", width, height); - //SetViewportSize((ndf32)width, (ndf32)height); - - return true; - } - else - { - throw std::runtime_error("emscripten_webgl_make_context_current failed"); - return false; - } + //int width = 0, height = 0, fs = 0; + //emscripten_webgl_get_drawing_buffer_size(context->context_, &width, &height); + + return true; } diff --git a/sources/Renderer/OpenGL/WebGLProfile/WebGLExtensionLoader.cpp b/sources/Renderer/OpenGL/WebGLProfile/WebGLExtensionLoader.cpp index d0cdac65ed..2221bd65f4 100644 --- a/sources/Renderer/OpenGL/WebGLProfile/WebGLExtensionLoader.cpp +++ b/sources/Renderer/OpenGL/WebGLProfile/WebGLExtensionLoader.cpp @@ -20,25 +20,25 @@ namespace LLGL // Global member to store if the extension have already been loaded -static bool g_OpenGLESExtensionsLoaded = false; -static std::set g_supportedOpenGLESExtensions; -static std::set g_loadedOpenGLESExtensions; +static bool g_WebGLExtensionsLoaded = false; +static std::set g_supportedWebGLExtensions; +static std::set g_loadedWebGLExtensions; -static void EnableGLESExtension(GLExt ext, const char* name) +static void EnableWebGLExtension(GLExt ext, const char* name) { RegisterExtension(ext); - g_supportedOpenGLESExtensions.insert(name); //TODO: find better way to determine supported GLES extensions - g_loadedOpenGLESExtensions.insert(name); + g_supportedWebGLExtensions.insert(name); //TODO: find better way to determine supported GLES extensions + g_loadedWebGLExtensions.insert(name); } bool LoadSupportedOpenGLExtensions(bool isCoreProfile, bool abortOnFailure) { /* Only load GL extensions once */ - if (g_OpenGLESExtensionsLoaded) + if (g_WebGLExtensionsLoaded) return true; #define ENABLE_GLEXT(NAME) \ - EnableGLESExtension(GLExt::NAME, "GL_" #NAME) + EnableWebGLExtension(GLExt::NAME, "GL_" #NAME) const int version = GLGetVersion(); @@ -124,17 +124,17 @@ bool LoadSupportedOpenGLExtensions(bool isCoreProfile, bool abortOnFailure) bool AreOpenGLExtensionsLoaded() { - return g_OpenGLESExtensionsLoaded; + return g_WebGLExtensionsLoaded; } const std::set& GetSupportedOpenGLExtensions() { - return g_supportedOpenGLESExtensions; + return g_supportedWebGLExtensions; } const std::set& GetLoadedOpenGLExtensions() { - return g_loadedOpenGLESExtensions; + return g_loadedWebGLExtensions; } From 98bcc168f04db55dac1b12f98afedc00f3893f68 Mon Sep 17 00:00:00 2001 From: Laura Hermanns Date: Fri, 23 Aug 2024 14:26:31 -0400 Subject: [PATCH 13/15] [Wasm] Made pthreads optional. - Disable pthreads by default to avoid extra browser options; Chrome requires '--enable-features=SharedArrayBuffer' to support threads in wasm app. - Added --pthreads option to BuildWasm.sh script. --- BuildWasm.sh | 7 ++++++- CMakeLists.txt | 16 ++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/BuildWasm.sh b/BuildWasm.sh index f38738c5ba..c0578d9881 100644 --- a/BuildWasm.sh +++ b/BuildWasm.sh @@ -5,6 +5,7 @@ OUTPUT_DIR="build_wasm" CLEAR_CACHE=0 ENABLE_EXAMPLES="ON" ENABLE_TESTS="ON" +ENABLE_PTHREADS="OFF" BUILD_TYPE="Release" PROJECT_ONLY=0 VERBOSE=0 @@ -22,6 +23,7 @@ print_help() echo " -p, --project-only [=G] ... Build project with CMake generator (default is CodeBlocks)" echo " --no-examples ............. Exclude example projects" echo " --no-tests ................ Exclude test projects" + echo " --pthreads ................ Enable pthreads (limits browser availability)" echo "NOTES:" echo " Default output directory is '$OUTPUT_DIR'" } @@ -53,7 +55,9 @@ for ARG in "$@"; do ENABLE_EXAMPLES="OFF" elif [ "$ARG" = "--no-tests" ]; then ENABLE_TESTS="OFF" - elif [ ! "$ARG" = "-msys" ]; then + elif [ "$ARG" = "--pthreads" ]; then + ENABLE_PTHREADS="ON" + else OUTPUT_DIR="$ARG" fi done @@ -121,6 +125,7 @@ OPTIONS=( -DLLGL_BUILD_EXAMPLES=$ENABLE_EXAMPLES -DLLGL_BUILD_TESTS=$ENABLE_TESTS -DLLGL_BUILD_STATIC_LIB=ON + -DLLGL_ENABLE_EMSCRIPTEN_PTHREADS=$ENABLE_PTHREADS -DGaussLib_INCLUDE_DIR:STRING="$GAUSSIAN_LIB_DIR" -S "$SOURCE_DIR" -B "$OUTPUT_DIR" diff --git a/CMakeLists.txt b/CMakeLists.txt index 92874e31b0..b9a18a5feb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -384,6 +384,7 @@ endif() set(SUMMARY_LIBRARY_TYPE "Unknown") set(SUMMARY_TARGET_ARCH "Unknown") +set(SUMMARY_FLAGS "") # === Options === @@ -414,6 +415,7 @@ option(LLGL_BUILD_RENDERER_NULL "Include Null renderer project" ON) if(NOT LLGL_UWP_PLATFORM) if(EMSCRIPTEN) option(LLGL_BUILD_RENDERER_WEBGL "Include WebGL renderer project" ON) + option(LLGL_ENABLE_EMSCRIPTEN_PTHREADS "Build for Wasm platform with pthreads (USE_PTHREADS). This limits browser availability!" OFF) elseif(LLGL_MOBILE_PLATFORM) option(LLGL_BUILD_RENDERER_OPENGLES3 "Include OpenGLES 3 renderer project" ON) else() @@ -490,8 +492,14 @@ else() endif() if(EMSCRIPTEN) - add_compile_options("SHELL:-s USE_PTHREADS") - add_link_options("SHELL:-s USE_PTHREADS") + # When USE_PTHREADS is enabled, HTML5 pages cannot be opened in Chrome unless launched with '--enable-features=SharedArrayBuffer' + if(LLGL_ENABLE_EMSCRIPTEN_PTHREADS) + add_compile_options("SHELL:-s USE_PTHREADS") + add_link_options("SHELL:-s USE_PTHREADS") + set(SUMMARY_FLAGS ${SUMMARY_FLAGS} "pthreads") + endif() + + # TODO: Emscripten file system pulls in a large amount of code. Consider limiting file system support to examples and tests. add_link_options("SHELL:-s FORCE_FILESYSTEM") # for examples # LLGL needs at least WebGL 2.0 @@ -848,5 +856,9 @@ if(LLGL_VK_ENABLE_SPIRV_REFLECT) message(STATUS "Including Submodule: SPIRV-Headers") endif() +if(NOT "${SUMMARY_FLAGS}" STREQUAL "") + message(STATUS "Options: ${SUMMARY_FLAGS}") +endif() + message(STATUS "~~~~~~~~~~~~~~~~~~~~~") From afaca3e1d4ed02caac1f582022fea0037f37377f Mon Sep 17 00:00:00 2001 From: Laura Hermanns Date: Fri, 23 Aug 2024 14:27:44 -0400 Subject: [PATCH 14/15] [WebGL] Fixed A8UNorm texture format for WebGL. WebGL does not support texture swizzling but does instead support the texture format GL_ALPHA (deprecated in GL 3.2 core). --- examples/Cpp/ExampleBase/Wasm/index.html | 4 ++-- sources/Renderer/OpenGL/GLTypes.cpp | 16 +++++++++++++++- sources/Renderer/OpenGL/Texture/GLTexture.cpp | 4 ++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/examples/Cpp/ExampleBase/Wasm/index.html b/examples/Cpp/ExampleBase/Wasm/index.html index b0ebcd828e..5e24eacf57 100644 --- a/examples/Cpp/ExampleBase/Wasm/index.html +++ b/examples/Cpp/ExampleBase/Wasm/index.html @@ -8,10 +8,10 @@ canvas { display: block; position: absolute; - top: 0; - bottom: 0; left: 0; + top: 0; right: 0; + bottom: 0; height: 100%; width: 100%; } diff --git a/sources/Renderer/OpenGL/GLTypes.cpp b/sources/Renderer/OpenGL/GLTypes.cpp index 73c532c0e1..f8791b96cc 100644 --- a/sources/Renderer/OpenGL/GLTypes.cpp +++ b/sources/Renderer/OpenGL/GLTypes.cpp @@ -34,7 +34,11 @@ GLenum MapOrZero(const Format format) case Format::Undefined: return 0; /* --- Alpha channel color formats --- */ + #ifdef LLGL_WEBGL + case Format::A8UNorm: return GL_ALPHA; + #else case Format::A8UNorm: return GL_R8; // texture swizzle + #endif /* --- Red channel color formats --- */ case Format::R8UNorm: return GL_R8; @@ -119,11 +123,13 @@ GLenum MapOrZero(const Format format) case Format::RGBA64Float: return 0; /* --- BGRA color formats --- */ + #ifndef LLGL_WEBGL // WebGL does not support texture swizzling case Format::BGRA8UNorm: return GL_RGBA8; // texture swizzle case Format::BGRA8UNorm_sRGB: return GL_SRGB8_ALPHA8; // texture swizzle case Format::BGRA8SNorm: return GL_RGBA8_SNORM; // texture swizzle case Format::BGRA8UInt: return GL_RGBA8UI; // texture swizzle case Format::BGRA8SInt: return GL_RGBA8I; // texture swizzle + #endif #ifdef LLGL_OPENGL /* --- Packed formats --- */ @@ -249,7 +255,7 @@ GLenum Map(const TextureSwizzle textureSwizzle) GLenum Map(const Format textureFormat) { - if (auto result = MapOrZero(textureFormat)) + if (GLenum result = MapOrZero(textureFormat)) return result; LLGL_TRAP_GL_MAP(Format, textureFormat); } @@ -258,7 +264,11 @@ static GLenum MapImageFormat(const ImageFormat imageFormat) { switch (imageFormat) { + #ifdef LLGL_WEBGL + case ImageFormat::Alpha: return GL_ALPHA; + #else case ImageFormat::Alpha: return GL_RED; // texture swizzle + #endif case ImageFormat::R: return GL_RED; case ImageFormat::RG: return GL_RG; case ImageFormat::RGB: return GL_RGB; @@ -288,7 +298,11 @@ static GLenum MapIntegerImageFormat(const ImageFormat imageFormat) { switch (imageFormat) { + #ifdef LLGL_WEBGL + case ImageFormat::Alpha: break; // WebGL does not support texture swizzling, only GL_ALPHA but it's not an integer format + #else case ImageFormat::Alpha: return GL_RED_INTEGER; // texture swizzle + #endif case ImageFormat::R: return GL_RED_INTEGER; case ImageFormat::RG: return GL_RG_INTEGER; case ImageFormat::RGB: return GL_RGB_INTEGER; diff --git a/sources/Renderer/OpenGL/Texture/GLTexture.cpp b/sources/Renderer/OpenGL/Texture/GLTexture.cpp index 6c9e4de3e3..988f8f8624 100644 --- a/sources/Renderer/OpenGL/Texture/GLTexture.cpp +++ b/sources/Renderer/OpenGL/Texture/GLTexture.cpp @@ -66,6 +66,9 @@ static bool IsRenderbufferSufficient(const TextureDescriptor& desc) // Maps the specified format to a swizzle format, or identity swizzle if texture swizzling is not necessary static GLSwizzleFormat MapToGLSwizzleFormat(const Format format) { + #ifdef LLGL_WEBGL + return GLSwizzleFormat::RGBA; // WebGL does not support texture swizzling + #else const auto& formatDesc = GetFormatAttribs(format); if (formatDesc.format == ImageFormat::Alpha) return GLSwizzleFormat::Alpha; @@ -73,6 +76,7 @@ static GLSwizzleFormat MapToGLSwizzleFormat(const Format format) return GLSwizzleFormat::BGRA; else return GLSwizzleFormat::RGBA; + #endif } GLTexture::GLTexture(const TextureDescriptor& desc) : From 22496dcb506dd1bef4dac2e5f80c8aed9904b5c2 Mon Sep 17 00:00:00 2001 From: Laura Hermanns Date: Fri, 23 Aug 2024 15:09:45 -0400 Subject: [PATCH 15/15] [Wasm] Resize Wasm canvas to initial CSS element size. Otherwise, the canvas will remain in its initial size, e.g. via , instead of adjusting to the browser window if the CSS style demands it. --- BuildWasm.sh | 11 ++-- sources/Platform/Wasm/WasmCanvas.cpp | 75 ++++++---------------------- sources/Platform/Wasm/WasmCanvas.h | 8 +-- 3 files changed, 25 insertions(+), 69 deletions(-) diff --git a/BuildWasm.sh b/BuildWasm.sh index c0578d9881..e5f6c5f68a 100644 --- a/BuildWasm.sh +++ b/BuildWasm.sh @@ -164,7 +164,9 @@ generate_html5_page() cp "$SOURCE_DIR/examples/Cpp/ExampleBase/Wasm/index.html" "$HTML5_ROOT/index.html" cp "$BIN_ROOT/$BASE_FILENAME.js" "$HTML5_ROOT/$BASE_FILENAME.js" cp "$BIN_ROOT/$BASE_FILENAME.wasm" "$HTML5_ROOT/$BASE_FILENAME.wasm" - cp "$BIN_ROOT/$BASE_FILENAME.worker.js" "$HTML5_ROOT/$BASE_FILENAME.worker.js" + if [ $ENABLE_PTHREADS = "ON" ]; then + cp "$BIN_ROOT/$BASE_FILENAME.worker.js" "$HTML5_ROOT/$BASE_FILENAME.worker.js" + fi # Replace meta data sed -i "s/LLGL_EXAMPLE_NAME/${CURRENT_PROJECT}/" "$HTML5_ROOT/index.html" @@ -214,11 +216,10 @@ if [ $PROJECT_ONLY -eq 0 ] && [ $ENABLE_EXAMPLES == "ON" ]; then BIN_FILE_BASE="${OUTPUT_DIR}/build/Example_" BIN_FILE_BASE_LEN=${#BIN_FILE_BASE} - EXAMPLE_BIN_FILES_D=(${BIN_FILE_BASE}*D.wasm) - if [ -z "$EXAMPLE_BIN_FILES_D" ]; then - EXAMPLE_BIN_FILES=(${BIN_FILE_BASE}*.wasm) + if [ $BUILD_TYPE = "Debug" ]; then + EXAMPLE_BIN_FILES=(${BIN_FILE_BASE}*D.wasm) else - EXAMPLE_BIN_FILES=(${EXAMPLE_BIN_FILES_D[@]}) + EXAMPLE_BIN_FILES=(${BIN_FILE_BASE}*.wasm) fi for BIN_FILE in ${EXAMPLE_BIN_FILES[@]}; do diff --git a/sources/Platform/Wasm/WasmCanvas.cpp b/sources/Platform/Wasm/WasmCanvas.cpp index 419db79ca9..3c7f22cd61 100644 --- a/sources/Platform/Wasm/WasmCanvas.cpp +++ b/sources/Platform/Wasm/WasmCanvas.cpp @@ -10,7 +10,8 @@ #include #include #include "../../Core/CoreUtils.h" -#include + +#include namespace LLGL @@ -83,33 +84,7 @@ UTF8String WasmCanvas::GetTitle() const * ======= Private: ======= */ -/* -EM_JS(emscripten::EM_VAL, get_canvas, (), { - let canvas = document.getElementById("mycanvas"); - return Emval.toHandle(canvas); -}); -canvas_ = emscripten::val::take_ownership(get_canvas()); -*/ - -static const char* emscripten_event_type_to_string(int eventType) -{ - const char *events[] = { "(invalid)", "(none)", "keypress", "keydown", "keyup", "click", "mousedown", "mouseup", "dblclick", "mousemove", "wheel", "resize", - "scroll", "blur", "focus", "focusin", "focusout", "deviceorientation", "devicemotion", "orientationchange", "fullscreenchange", "pointerlockchange", - "visibilitychange", "touchstart", "touchend", "touchmove", "touchcancel", "gamepadconnected", "gamepaddisconnected", "beforeunload", - "batterychargingchange", "batterylevelchange", "webglcontextlost", "webglcontextrestored", "(invalid)" }; - - ++eventType; - - if (eventType < 0) - eventType = 0; - - if (eventType >= sizeof(events)/sizeof(events[0])) - eventType = sizeof(events)/sizeof(events[0])-1; - - return events[eventType]; -} - -static const char* EmscriptenResultToString(EMSCRIPTEN_RESULT result) +/*static const char* EmscriptenResultToString(EMSCRIPTEN_RESULT result) { switch (result) { @@ -124,9 +99,9 @@ static const char* EmscriptenResultToString(EMSCRIPTEN_RESULT result) case EMSCRIPTEN_RESULT_NO_DATA: return "EMSCRIPTEN_RESULT_NO_DATA"; } return "Unknown EMSCRIPTEN_RESULT!"; -} +}*/ -static int interpret_charcode_for_keyevent(int eventType, const EmscriptenKeyboardEvent* event) +/*static int EmscriptenKeyeventToCharcode(int eventType, const EmscriptenKeyboardEvent* event) { // Only KeyPress events carry a charCode. For KeyDown and KeyUp events, these don't seem to be present yet, until later when the KeyDown // is transformed to KeyPress. Sometimes it can be useful to already at KeyDown time to know what the charCode of the resulting @@ -136,13 +111,13 @@ static int interpret_charcode_for_keyevent(int eventType, const EmscriptenKeyboa if (strlen(event->key) == 1) return (int)event->key[0]; if (event->which) return event->which; return event->keyCode; -} +}*/ -const char* WasmCanvas::OnBeforeUnloadCallback(int eventType, const void* reserved, void* userData) +const char* WasmCanvas::OnBeforeUnloadCallback(int eventType, const void* /*reserved*/, void* userData) { WasmCanvas* canvas = reinterpret_cast(userData); canvas->PostDestroy(); - return nullptr; + return nullptr; // no string to be displayed to the user } EM_BOOL WasmCanvas::OnCanvasResizeCallback(int eventType, const EmscriptenUiEvent* event, void* userData) @@ -169,20 +144,7 @@ EM_BOOL WasmCanvas::OnKeyCallback(int eventType, const EmscriptenKeyboardEvent* { WasmCanvas* canvas = reinterpret_cast(userData); - int dom_pk_code = emscripten_compute_dom_pk_code(event->code); - - /* - printf("%s, key: \"%s\", code: \"%s\" = %s (%d), location: %lu,%s%s%s%s repeat: %d, locale: \"%s\", char: \"%s\", charCode: %lu (interpreted: %d), keyCode: %s(%lu), which: %lu\n", - emscripten_event_type_to_string(eventType), event->key, event->code, emscripten_dom_pk_code_to_string(dom_pk_code), dom_pk_code, event->location, - event->ctrlKey ? " CTRL" : "", event->shiftKey ? " SHIFT" : "", event->altKey ? " ALT" : "", event->metaKey ? " META" : "", - event->repeat, event->locale, event->charValue, event->charCode, interpret_charcode_for_keyevent(eventType, e), emscripten_dom_vk_to_string(event->keyCode), event->keyCode, event->which); - - if (eventType == EMSCRIPTEN_EVENT_KEYUP) printf("\n"); // Visual cue - */ - - printf("%s, key: \"%s\", code: \"%s\" = %s (%d)\n", emscripten_event_type_to_string(eventType), event->key, event->code, emscripten_dom_pk_code_to_string(dom_pk_code), dom_pk_code); - - auto key = MapEmscriptenKeyCode(event->code); + Key key = MapEmscriptenKeyCode(event->code); if (eventType == 2) canvas->PostKeyDown(key); @@ -268,19 +230,7 @@ EM_BOOL WasmCanvas::OnMouseCallback(int eventType, const EmscriptenMouseEvent* e EM_BOOL WasmCanvas::OnWheelCallback(int eventType, const EmscriptenWheelEvent* event, void* userData) { - /* - printf("%s, screen: (%ld,%ld), client: (%ld,%ld),%s%s%s%s button: %hu, buttons: %hu, canvas: (%ld,%ld), target: (%ld, %ld), delta:(%g,%g,%g), deltaMode:%lu\n", - emscripten_event_type_to_string(eventType), event->mouse.screenX, event->mouse.screenY, event->mouse.clientX, event->mouse.clientY, - event->mouse.ctrlKey ? " CTRL" : "", event->mouse.shiftKey ? " SHIFT" : "", event->mouse.altKey ? " ALT" : "", event->mouse.metaKey ? " META" : "", - event->mouse.button, event->mouse.buttons, event->mouse.canvasX, event->mouse.canvasY, event->mouse.targetX, event->mouse.targetY, - (float)event->deltaX, (float)event->deltaY, (float)event->deltaZ, event->deltaMode); - */ - - /* - if (event->deltaY > 0.f || event->deltaY < 0.f) - gotWheel = 1; - */ - + //TODO return EM_TRUE; } @@ -323,6 +273,11 @@ void WasmCanvas::CreateEmscriptenCanvas(const CanvasDescriptor& desc) ret = emscripten_set_mouseenter_callback(/*canvasSelector.c_str()*/EMSCRIPTEN_EVENT_TARGET_WINDOW, this, EM_TRUE, WasmCanvas::OnMouseCallback); ret = emscripten_set_mouseleave_callback(/*canvasSelector.c_str()*/EMSCRIPTEN_EVENT_TARGET_WINDOW, this, EM_TRUE, WasmCanvas::OnMouseCallback); ret = emscripten_set_wheel_callback (/*canvasSelector.c_str()*/EMSCRIPTEN_EVENT_TARGET_WINDOW, this, EM_TRUE, WasmCanvas::OnWheelCallback); + + /* Resize canvas to initial CSS size */ + double w = 0, h = 0; + emscripten_get_element_css_size(canvasSelector.c_str(), &w, &h); + emscripten_set_canvas_element_size(canvasSelector.c_str(), static_cast(w), static_cast(h)); } diff --git a/sources/Platform/Wasm/WasmCanvas.h b/sources/Platform/Wasm/WasmCanvas.h index c8a4ebe28a..bcc94a4025 100644 --- a/sources/Platform/Wasm/WasmCanvas.h +++ b/sources/Platform/Wasm/WasmCanvas.h @@ -42,12 +42,12 @@ class WasmCanvas : public Canvas private: static const char* OnBeforeUnloadCallback(int eventType, const void* reserved, void* userData); - static EM_BOOL OnCanvasResizeCallback(int eventType, const EmscriptenUiEvent *keyEvent, void *userData); + static EM_BOOL OnCanvasResizeCallback(int eventType, const EmscriptenUiEvent* event, void *userData); - static EM_BOOL OnKeyCallback(int eventType, const EmscriptenKeyboardEvent *e, void *userData); + static EM_BOOL OnKeyCallback(int eventType, const EmscriptenKeyboardEvent* event, void *userData); - static EM_BOOL OnMouseCallback(int eventType, const EmscriptenMouseEvent *e, void *userData); - static EM_BOOL OnWheelCallback(int eventType, const EmscriptenWheelEvent *e, void *userData); + static EM_BOOL OnMouseCallback(int eventType, const EmscriptenMouseEvent* event, void *userData); + static EM_BOOL OnWheelCallback(int eventType, const EmscriptenWheelEvent* event, void *userData); private: