diff --git a/examples/DeferredShading/main.cpp b/examples/DeferredShading/main.cpp index 7622aa88a..a0a501d4d 100644 --- a/examples/DeferredShading/main.cpp +++ b/examples/DeferredShading/main.cpp @@ -468,7 +468,7 @@ int main() std::vector> bloomBlendShaderBinding(BloomSubdivisionCount); // Gamma correction - + Nz::RenderPipelineLayoutInfo fullscreenPipelineLayoutInfo; fullscreenPipelineLayoutInfo.bindings.push_back({ @@ -683,7 +683,7 @@ int main() "Color", Nz::PixelFormat::RGBA8 }); - + normalTexture = graph.AddAttachment({ "Normal", Nz::PixelFormat::RGBA8 @@ -698,7 +698,7 @@ int main() "Depth buffer", depthStencilFormat }); - + depthBuffer2 = graph.AddAttachment({ "Depth buffer", depthStencilFormat @@ -891,7 +891,7 @@ int main() forwardPass.SetDepthStencilInput(depthBuffer1); forwardPass.SetDepthStencilOutput(depthBuffer2); - + Nz::FramePass& occluderPass = graph.AddPass("Occluder pass"); occluderPass.SetCommandCallback([&](Nz::CommandBufferBuilder& builder, const Nz::FramePassEnvironment& env) { @@ -979,7 +979,7 @@ int main() bloomBlurPassHorizontal.AddInput((i == 0) ? bloomBrightOutput : bloomTextures[bloomTextureIndex++]); bloomBlurPassHorizontal.AddOutput(bloomTextures[bloomTextureIndex]); - + Nz::FramePass& bloomBlurPassVertical = graph.AddPass("Bloom pass - gaussian blur #" + std::to_string(i) + " - vertical"); bloomBlurPassVertical.SetCommandCallback([&, i](Nz::CommandBufferBuilder& builder, const Nz::FramePassEnvironment& env) { diff --git a/examples/PhysicallyBasedRendering/xmake.lua b/examples/PhysicallyBasedRendering/xmake.lua index 6af2278d6..5093554b6 100644 --- a/examples/PhysicallyBasedRendering/xmake.lua +++ b/examples/PhysicallyBasedRendering/xmake.lua @@ -1,3 +1,7 @@ target("PBR") add_deps("NazaraGraphics") add_files("main.cpp") + + if is_plat("wasm") then + add_ldflags("--preload-file assets/", {force = true}) + end diff --git a/examples/WidgetDemo/main.cpp b/examples/WidgetDemo/main.cpp index f4d63a200..54a917dbd 100644 --- a/examples/WidgetDemo/main.cpp +++ b/examples/WidgetDemo/main.cpp @@ -98,7 +98,7 @@ int main() longTextArea->SetBackgroundColor(Nz::Color::White()); longTextArea->SetTextColor(Nz::Color::Black()); longTextArea->SetText("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"); - + Nz::ScrollAreaWidget* scrollBarWidget = canvas2D.Add(longTextArea); scrollBarWidget->SetPosition(1400.f, 800.f); scrollBarWidget->Resize({ 512.f, 256.f }); diff --git a/include/Nazara/Core/DynLib.hpp b/include/Nazara/Core/DynLib.hpp index d691f38ba..4c31da10f 100644 --- a/include/Nazara/Core/DynLib.hpp +++ b/include/Nazara/Core/DynLib.hpp @@ -16,6 +16,8 @@ #define NAZARA_DYNLIB_EXTENSION ".dll" #elif defined(NAZARA_PLATFORM_LINUX) #define NAZARA_DYNLIB_EXTENSION ".so" +#elif defined(NAZARA_PLATFORM_WEB) + #define NAZARA_DYNLIB_EXTENSION ".wasm" #elif defined(NAZARA_PLATFORM_MACOS) #define NAZARA_DYNLIB_EXTENSION ".dylib" #else diff --git a/include/Nazara/OpenGLRenderer/Wrapper/Web/WebContext.hpp b/include/Nazara/OpenGLRenderer/Wrapper/Web/WebContext.hpp new file mode 100644 index 000000000..014f47c76 --- /dev/null +++ b/include/Nazara/OpenGLRenderer/Wrapper/Web/WebContext.hpp @@ -0,0 +1,76 @@ +// Copyright (C) 2022 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "Nazara Engine - OpenGL renderer" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_OPENGLRENDERER_WRAPPER_WEB_WEBCONTEXT_HPP +#define NAZARA_OPENGLRENDERER_WRAPPER_WEB_WEBCONTEXT_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Nz::GL +{ + class WebLoader; + + class NAZARA_OPENGLRENDERER_API WebContext : public Context + { + public: + inline WebContext(const OpenGLDevice* device, const WebLoader& loader); + WebContext(const WebContext&) = delete; + WebContext(WebContext&&) = delete; + ~WebContext(); + + virtual bool Create(const ContextParams& params, const WebContext* shareContext = nullptr); + virtual bool Create(const ContextParams& params, WindowHandle window, const WebContext* shareContext = nullptr); + virtual void Destroy(); + + void EnableVerticalSync(bool enabled) override; + + inline bool HasPlatformExtension(const std::string& str) const; + + void SwapBuffers() override; + + WebContext& operator=(const WebContext&) = delete; + WebContext& operator=(WebContext&&) = delete; + + protected: + bool ChooseConfig(EmscriptenWebGLContextAttributes* configs, std::size_t maxConfigCount, std::size_t* configCount); + bool CreateInternal(EmscriptenWebGLContextAttributes config, const WebContext* shareContext = nullptr); + + const WebLoader& m_loader; + + private: + bool ImplementFallback(const std::string_view& function) override; + + bool Activate() const override; + void Desactivate() const override; + const Loader& GetLoader() override; + bool LoadExt(); + + struct Fallback + { + using glClearDepthProc = void(*)(double depth); + + glClearDepthProc glClearDepth; + }; + Fallback fallbacks; //< m_ omitted + + std::unordered_set m_supportedPlatformExtensions; + static EMSCRIPTEN_WEBGL_CONTEXT_HANDLE s_handle; + static size_t s_handleCounter; + bool m_ownsDisplay; + }; +} + +#include + +#endif // NAZARA_OPENGLRENDERER_WRAPPER_WEB_WEBCONTEXT_HPP diff --git a/include/Nazara/OpenGLRenderer/Wrapper/Web/WebContext.inl b/include/Nazara/OpenGLRenderer/Wrapper/Web/WebContext.inl new file mode 100644 index 000000000..648a856b8 --- /dev/null +++ b/include/Nazara/OpenGLRenderer/Wrapper/Web/WebContext.inl @@ -0,0 +1,24 @@ +// Copyright (C) 2022 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "Nazara Engine - OpenGL renderer" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include + +namespace Nz::GL +{ + inline WebContext::WebContext(const OpenGLDevice* device, const WebLoader& loader) : + Context(device), + m_loader(loader), + //m_handle(0), + m_ownsDisplay(false) + { + } + + inline bool WebContext::HasPlatformExtension(const std::string& str) const + { + return m_supportedPlatformExtensions.find(str) != m_supportedPlatformExtensions.end(); + } +} + +#include diff --git a/include/Nazara/OpenGLRenderer/Wrapper/Web/WebLoader.hpp b/include/Nazara/OpenGLRenderer/Wrapper/Web/WebLoader.hpp new file mode 100644 index 000000000..4f08ff10c --- /dev/null +++ b/include/Nazara/OpenGLRenderer/Wrapper/Web/WebLoader.hpp @@ -0,0 +1,40 @@ +// Copyright (C) 2022 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "Nazara Engine - OpenGL renderer" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_OPENGLRENDERER_WRAPPER_WEB_WEBLOADER_HPP +#define NAZARA_OPENGLRENDERER_WRAPPER_WEB_WEBLOADER_HPP + +#include +#include +#include +#include +#include + +namespace Nz::GL +{ + class NAZARA_OPENGLRENDERER_API WebLoader : public Loader + { + struct SymbolLoader; + friend SymbolLoader; + + public: + std::unique_ptr CreateContext(const OpenGLDevice* device, const ContextParams& params, Context* shareContext) const override; + std::unique_ptr CreateContext(const OpenGLDevice* device, const ContextParams& params, WindowHandle handle, Context* shareContext) const override; + + ContextType GetPreferredContextType() const override; + + GLFunction LoadFunction(const char* name) const override; + + static const char* TranslateError(EMSCRIPTEN_RESULT errorId); + + private: + bool ImplementFallback(const std::string_view& function); + }; +} + +#include + +#endif // NAZARA_OPENGLRENDERER_WRAPPER_WEB_WEBLOADER_HPP diff --git a/include/Nazara/OpenGLRenderer/Wrapper/Web/WebLoader.inl b/include/Nazara/OpenGLRenderer/Wrapper/Web/WebLoader.inl new file mode 100644 index 000000000..135dd3310 --- /dev/null +++ b/include/Nazara/OpenGLRenderer/Wrapper/Web/WebLoader.inl @@ -0,0 +1,12 @@ +// Copyright (C) 2022 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "Nazara Engine - OpenGL renderer" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include + +namespace Nz::GL +{ +} + +#include diff --git a/include/Nazara/Platform/WindowHandle.hpp b/include/Nazara/Platform/WindowHandle.hpp index feb6af244..7e9d93c1a 100644 --- a/include/Nazara/Platform/WindowHandle.hpp +++ b/include/Nazara/Platform/WindowHandle.hpp @@ -19,7 +19,8 @@ namespace Nz Cocoa, X11, Wayland, - Windows + Windows, + Web }; struct WindowHandle @@ -39,7 +40,7 @@ namespace Nz unsigned long window; //< Window } x11; - struct + struct { void* display; //< wl_display* void* surface; //< wl_surface* @@ -50,6 +51,7 @@ namespace Nz { void* window; //< HWND } windows; + }; }; } diff --git a/include/Nazara/Utility/BasicMainloop.hpp b/include/Nazara/Utility/BasicMainloop.hpp new file mode 100644 index 000000000..359b5c978 --- /dev/null +++ b/include/Nazara/Utility/BasicMainloop.hpp @@ -0,0 +1,43 @@ +// Copyright (C) 2022 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "Nazara Engine - Utility module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_BASIC_MAINLOOP_HPP +#define NAZARA_BASIC_MAINLOOP_HPP + +#include + +#ifdef NAZARA_PLATFORM_WEB +#include +#endif + +namespace Nz +{ + template + void BasicMainloop(RenderWindow& window, CallbackT&& callback) + { +#ifndef NAZARA_PLATFORM_WEB + while (window.IsOpen()) + { + callback(); + } +#else + emscripten_set_main_loop_arg([] (void* callbackInstance) + { + try + { + (*static_cast(callbackInstance))(); + } + catch(std::exception e) + { + NazaraDebug(e.what()); + } + }, &callback, 0, 1); +#endif + } +} + + +#endif // NAZARA_BASIC_MAINLOOP_HPP diff --git a/src/Nazara/Core/Posix/FileImpl.hpp b/src/Nazara/Core/Posix/FileImpl.hpp index 44bc4d88f..f830f6298 100644 --- a/src/Nazara/Core/Posix/FileImpl.hpp +++ b/src/Nazara/Core/Posix/FileImpl.hpp @@ -23,7 +23,7 @@ #define Lseek lseek #define Open_def open #define Ftruncate ftruncate -#elif defined(NAZARA_PLATFORM_LINUX) +#elif defined(NAZARA_PLATFORM_LINUX) || defined(NAZARA_PLATFORM_WEB) #define Stat stat64 #define Fstat fstat64 #define Off_t off64_t diff --git a/src/Nazara/Core/Posix/HardwareInfoImpl.cpp b/src/Nazara/Core/Posix/HardwareInfoImpl.cpp index 0f97bd6f1..e150b6ebf 100644 --- a/src/Nazara/Core/Posix/HardwareInfoImpl.cpp +++ b/src/Nazara/Core/Posix/HardwareInfoImpl.cpp @@ -5,12 +5,13 @@ #include #include #include +#include namespace Nz { void HardwareInfoImpl::Cpuid(UInt32 functionId, UInt32 subFunctionId, UInt32 registers[4]) { - #if defined(NAZARA_COMPILER_CLANG) || defined(NAZARA_COMPILER_GCC) || defined(NAZARA_COMPILER_INTEL) + #if (defined(NAZARA_COMPILER_CLANG) || defined(NAZARA_COMPILER_GCC) || defined(NAZARA_COMPILER_INTEL)) && !defined(NAZARA_PLATFORM_WEB) // https://en.wikipedia.org/wiki/CPUID asm volatile( #ifdef NAZARA_PLATFORM_x64 @@ -50,6 +51,8 @@ namespace Nz { #ifdef NAZARA_PLATFORM_x64 return true; // cpuid is always supported on x64 arch + #elif defined(NAZARA_PLATFORM_WEB) + return false; #else #if defined(NAZARA_COMPILER_CLANG) || defined(NAZARA_COMPILER_GCC) || defined(NAZARA_COMPILER_INTEL) int supported; diff --git a/src/Nazara/Core/Stream.cpp b/src/Nazara/Core/Stream.cpp index 51af94e29..0814d325b 100644 --- a/src/Nazara/Core/Stream.cpp +++ b/src/Nazara/Core/Stream.cpp @@ -135,7 +135,7 @@ namespace Nz * Reads the stream until a line separator or the end of the stream is found. * * If lineSize does not equal zero, it represents the maximum character count to be read from the stream. - * + * * \param lineSize Maximum number of characters to read, or zero for no limit * * \return Line read from file @@ -264,7 +264,7 @@ namespace Nz #if defined(NAZARA_PLATFORM_WINDOWS) std::string temp(string); ReplaceStr(temp, "\n", "\r\n"); -#elif defined(NAZARA_PLATFORM_LINUX) +#elif defined(NAZARA_PLATFORM_LINUX) || defined(NAZARA_PLATFORM_WEB) std::string_view temp(string); // Nothing to do #elif defined(NAZARA_PLATFORM_MACOS) diff --git a/src/Nazara/Core/Uuid.cpp b/src/Nazara/Core/Uuid.cpp index 9f036a57e..5bd1dc767 100644 --- a/src/Nazara/Core/Uuid.cpp +++ b/src/Nazara/Core/Uuid.cpp @@ -7,7 +7,7 @@ #ifdef NAZARA_PLATFORM_WINDOWS #include -#elif defined(NAZARA_PLATFORM_LINUX) || defined(NAZARA_PLATFORM_MACOS) +#elif defined(NAZARA_PLATFORM_LINUX) || defined(NAZARA_PLATFORM_MACOS) || defined(NAZARA_PLATFORM_WEB) #include #endif @@ -44,7 +44,7 @@ namespace Nz for (unsigned int i = 0; i < 8; ++i) uuid[8 + i] = static_cast(id.Data4[i]); -#elif defined(NAZARA_PLATFORM_LINUX) || defined(NAZARA_PLATFORM_MACOS) +#elif defined(NAZARA_PLATFORM_LINUX) || defined(NAZARA_PLATFORM_MACOS) || defined(NAZARA_PLATFORM_WEB) uuid_t id; uuid_generate(id); diff --git a/src/Nazara/OpenGLRenderer/OpenGLCommandBuffer.cpp b/src/Nazara/OpenGLRenderer/OpenGLCommandBuffer.cpp index fdbc6dfe0..24830e1ea 100644 --- a/src/Nazara/OpenGLRenderer/OpenGLCommandBuffer.cpp +++ b/src/Nazara/OpenGLRenderer/OpenGLCommandBuffer.cpp @@ -322,8 +322,8 @@ namespace Nz } } - if (!invalidateAttachments.empty()) - context->glInvalidateFramebuffer(GL_FRAMEBUFFER, GLsizei(invalidateAttachments.size()), invalidateAttachments.data()); + /*if (!invalidateAttachments.empty()) + context->glInvalidateFramebuffer(GL_FRAMEBUFFER, GLsizei(invalidateAttachments.size()), invalidateAttachments.data());*/ } else static_assert(AlwaysFalse::value, "non-exhaustive visitor"); diff --git a/src/Nazara/OpenGLRenderer/OpenGLRenderer.cpp b/src/Nazara/OpenGLRenderer/OpenGLRenderer.cpp index 554d97ac3..9e45a6069 100644 --- a/src/Nazara/OpenGLRenderer/OpenGLRenderer.cpp +++ b/src/Nazara/OpenGLRenderer/OpenGLRenderer.cpp @@ -14,6 +14,10 @@ #include #endif +#if defined(NAZARA_PLATFORM_WEB) +#include +#endif + #ifdef NAZARA_PLATFORM_WINDOWS #include #endif @@ -82,6 +86,17 @@ namespace Nz } #endif +#if defined(NAZARA_PLATFORM_WEB) + try + { + return std::make_unique(); + } + catch (const std::exception& e) + { + NazaraWarning(std::string("Failed to load WebGL: ") + e.what()); + } +#endif + return {}; } diff --git a/src/Nazara/OpenGLRenderer/OpenGLTexture.cpp b/src/Nazara/OpenGLRenderer/OpenGLTexture.cpp index ddafa14fe..75871d9c7 100644 --- a/src/Nazara/OpenGLRenderer/OpenGLTexture.cpp +++ b/src/Nazara/OpenGLRenderer/OpenGLTexture.cpp @@ -54,11 +54,13 @@ namespace Nz if (!context.DidLastCallSucceed()) throw std::runtime_error("failed to create texture"); +#ifndef NAZARA_PLATFORM_WEB m_texture.SetParameteri(GL_TEXTURE_MAX_LEVEL, m_textureInfo.levelCount); m_texture.SetParameteri(GL_TEXTURE_SWIZZLE_R, format->swizzleR); m_texture.SetParameteri(GL_TEXTURE_SWIZZLE_G, format->swizzleG); m_texture.SetParameteri(GL_TEXTURE_SWIZZLE_B, format->swizzleB); m_texture.SetParameteri(GL_TEXTURE_SWIZZLE_A, format->swizzleA); +#endif } OpenGLTexture::OpenGLTexture(std::shared_ptr parentTexture, const TextureViewInfo& viewInfo) : diff --git a/src/Nazara/OpenGLRenderer/Wrapper/Context.cpp b/src/Nazara/OpenGLRenderer/Wrapper/Context.cpp index 772839c88..6a859b179 100644 --- a/src/Nazara/OpenGLRenderer/Wrapper/Context.cpp +++ b/src/Nazara/OpenGLRenderer/Wrapper/Context.cpp @@ -130,6 +130,10 @@ namespace Nz::GL void Context::BindBuffer(BufferTarget target, GLuint buffer, bool force) const { +#ifdef NAZARA_PLATFORM_WEB + force = true; +#endif + if (m_state.bufferTargets[UnderlyingCast(target)] != buffer || force) { if (!SetCurrentContext(this)) @@ -162,7 +166,12 @@ namespace Nz::GL void Context::BindFramebuffer(FramebufferTarget target, GLuint fbo) const { auto& currentFbo = (target == FramebufferTarget::Draw) ? m_state.boundDrawFBO : m_state.boundReadFBO; - if (currentFbo != fbo) +#ifdef NAZARA_PLATFORM_WEB + constexpr bool isWeb = true; +#else + constexpr bool isWeb = false; +#endif + if (currentFbo != fbo || isWeb); { if (!SetCurrentContext(this)) throw std::runtime_error("failed to activate context"); @@ -427,7 +436,7 @@ namespace Nz::GL try { -#define NAZARA_OPENGLRENDERER_FUNC(name, sig) loader.Load(name, #name, true, true); +#define NAZARA_OPENGLRENDERER_FUNC(name, sig) loader.Load(name, #name, false, true); #define NAZARA_OPENGLRENDERER_EXT_FUNC(name, sig) //< Do nothing NAZARA_OPENGLRENDERER_FOREACH_GLES_FUNC(NAZARA_OPENGLRENDERER_FUNC, NAZARA_OPENGLRENDERER_EXT_FUNC) #undef NAZARA_OPENGLRENDERER_EXT_FUNC diff --git a/src/Nazara/OpenGLRenderer/Wrapper/Web/WebContext.cpp b/src/Nazara/OpenGLRenderer/Wrapper/Web/WebContext.cpp new file mode 100644 index 000000000..302cc902d --- /dev/null +++ b/src/Nazara/OpenGLRenderer/Wrapper/Web/WebContext.cpp @@ -0,0 +1,255 @@ +// Copyright (C) 2022 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "Nazara Engine - OpenGL renderer" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Nz::GL +{ + WebContext::~WebContext() + { + Destroy(); + } + + bool WebContext::Create(const ContextParams& params, const WebContext* shareContext) + { + Destroy(); //< In case a previous display or surface hasn't been released + + m_params = params; + + std::size_t configCount; + std::array configs; + if (!ChooseConfig(configs.data(), configs.size(), &configCount)) + return false; + + std::size_t configIndex = 0; + + return CreateInternal(configs[configIndex], shareContext); + } + + bool WebContext::Create(const ContextParams& params, WindowHandle /*window*/, const WebContext* /*shareContext*/) + { + /*NazaraError("Unexpected context creation call"); + return false;*/ + + return Create(params, nullptr); + } + + void WebContext::Destroy() + { + if (s_handle > 0) + { + assert(s_handle > 0); + + OnContextRelease(); + NotifyContextDestruction(this); + + s_handleCounter--; + + if(s_handleCounter == 0) + { + emscripten_webgl_destroy_context(s_handle); + s_handle = 0; + } + } + } + + void WebContext::EnableVerticalSync(bool /*enabled*/) + { + // TODO + } + + void WebContext::SwapBuffers() + { + emscripten_webgl_commit_frame(); + } + + bool WebContext::ChooseConfig(EmscriptenWebGLContextAttributes* configs, std::size_t maxConfigCount, std::size_t* configCount) + { + EmscriptenWebGLContextAttributes configAttributes; + emscripten_webgl_init_context_attributes(&configAttributes); + + // https://emscripten.org/docs/api_reference/html5.h.html#c.EmscriptenWebGLContextAttributes + configAttributes.alpha = true; + configAttributes.depth = m_params.depthBits > 0; + configAttributes.stencil = m_params.stencilBits > 0; + configAttributes.antialias = false; + configAttributes.premultipliedAlpha = true; + configAttributes.preserveDrawingBuffer = false; + configAttributes.powerPreference = EM_WEBGL_POWER_PREFERENCE_DEFAULT; + configAttributes.failIfMajorPerformanceCaveat = false; + configAttributes.majorVersion = m_params.glMajorVersion; + configAttributes.minorVersion = m_params.glMinorVersion; + configAttributes.enableExtensionsByDefault = true; + configAttributes.explicitSwapControl = false; // todo + configAttributes.renderViaOffscreenBackBuffer = false; // todo + configAttributes.proxyContextToMainThread = false; // todo + + size_t numConfig = 1; + + configs[0] = configAttributes; + + *configCount = numConfig; + + return true; + } + + bool WebContext::CreateInternal(EmscriptenWebGLContextAttributes config, const WebContext* shareContext) + { + if(s_handleCounter > 0) + { + s_handleCounter++; + return true; + } + + if (shareContext) + { + NazaraWarning(std::string("shared contexes are not supported by WebGL but shareContext is not null")); + } + + struct Version + { + unsigned int major; + unsigned int minor; + }; + + if (m_params.type == ContextType::OpenGL_ES) + { + // Create OpenGL ES context + std::array supportedGL_ESVersions = { + { + { 2, 0 }, + { 1, 0 } + } + }; + + for (const Version& version : supportedGL_ESVersions) + { + if (m_params.glMajorVersion != 0) + { + if (version.major > m_params.glMajorVersion) + continue; + + if (m_params.glMinorVersion != 0 && version.minor > m_params.glMinorVersion) + continue; + } + + config.majorVersion = version.major; + config.minorVersion = version.minor; + + s_handle = emscripten_webgl_create_context("canvas", &config); + if (s_handle > 0) + { + break; + } + } + } + else + { + NazaraError(std::string("failed to create WebGL context: OpenGL is not supported")); + return false; + } + + if (s_handle <= 0) + { + NazaraError(std::string("failed to create Web context: ") + WebLoader::TranslateError(static_cast(s_handle))); + return false; + } + + LoadExt(); + + s_handleCounter++; + return true; + } + + bool WebContext::ImplementFallback(const std::string_view& function) + { + if (Context::ImplementFallback(function)) + return true; + + if (m_params.type == ContextType::OpenGL_ES) + return false; //< Implement fallback only for OpenGL (when emulating OpenGL ES) + + if (function == "glClearDepthf") + { + fallbacks.glClearDepth = reinterpret_cast(m_loader.LoadFunction("glClearDepth")); + if (!fallbacks.glClearDepth) + return false; + + glClearDepthf = [](GLfloat depth) + { + const WebContext* context = static_cast(GetCurrentContext()); + assert(context); + context->fallbacks.glClearDepth(depth); + }; + } + + return true; + } + + bool WebContext::Activate() const + { + EMSCRIPTEN_RESULT succeeded = emscripten_webgl_make_context_current(s_handle); + if (succeeded != EMSCRIPTEN_RESULT_SUCCESS) + { + NazaraError("failed to activate context"); + return false; + } + + return true; + } + + void WebContext::Desactivate() const + { + assert(GetCurrentContext() == this); + EMSCRIPTEN_RESULT succeeded = emscripten_webgl_make_context_current(0); + if (succeeded != EMSCRIPTEN_RESULT_SUCCESS) + NazaraError("failed to desactivate context"); + } + + const Loader& WebContext::GetLoader() + { + return m_loader; + } + + bool WebContext::LoadExt() + { + if (!SetCurrentContext(this)) + return false; + + char* extensionString = emscripten_webgl_get_supported_extensions(); + if (extensionString) + { + SplitString(extensionString, " ", [&](std::string_view extension) + { + m_supportedPlatformExtensions.emplace(extension); + + std::string ext(extension); + + emscripten_webgl_enable_extension(s_handle, ext.c_str()); + + return true; + }); + } + + free(extensionString); + + return true; + } + + + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE WebContext::s_handle = 0; + size_t WebContext::s_handleCounter = 0; +} + +#if defined(NAZARA_PLATFORM_WINDOWS) +#include +#endif diff --git a/src/Nazara/OpenGLRenderer/Wrapper/Web/WebLoader.cpp b/src/Nazara/OpenGLRenderer/Wrapper/Web/WebLoader.cpp new file mode 100644 index 000000000..e3b238f6b --- /dev/null +++ b/src/Nazara/OpenGLRenderer/Wrapper/Web/WebLoader.cpp @@ -0,0 +1,142 @@ +// Copyright (C) 2022 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "Nazara Engine - OpenGL renderer" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include + +#include + +namespace Nz::GL +{ + struct WebLoader::SymbolLoader + { + SymbolLoader(WebLoader& parent) : + loader(parent) + { + } + + template + bool Load(Func& func, const char* funcName, bool mandatory, bool implementFallback = true) + { + func = LoadRaw(funcName); + if (!func) + { + if (!implementFallback || (!loader.ImplementFallback(funcName) && !func)) //< double-check + { + if (mandatory) + throw std::runtime_error("failed to load core function " + std::string(funcName)); + } + } + + return func != nullptr; + } + + template + FuncType LoadRaw(const char* funcName) + { + return reinterpret_cast(loader.LoadFunction(funcName)); + } + + WebLoader& loader; + }; + + std::unique_ptr WebLoader::CreateContext(const OpenGLDevice* device, const ContextParams& params, Context* shareContext) const + { + std::unique_ptr context = std::make_unique(device, *this); + + if (!context->Create(params, static_cast(shareContext))) + { + NazaraError("failed to create context"); + return {}; + } + + if (!context->Initialize(params)) + { + NazaraError("failed to initialize context"); + return {}; + } + + return context; + } + + std::unique_ptr WebLoader::CreateContext(const OpenGLDevice* device, const ContextParams& params, WindowHandle handle, Context* shareContext) const + { + std::unique_ptr context; + switch (handle.type) + { + case WindowBackend::Invalid: + break; + + case WindowBackend::Web: + context = std::make_unique(device, *this); + break; + } + + if (!context) + { + NazaraError("unsupported window type"); + return {}; + } + + if (!context->Create(params, handle, static_cast(shareContext))) + { + NazaraError("failed to create context"); + return {}; + } + + if (!context->Initialize(params)) + { + NazaraError("failed to initialize context"); + return {}; + } + + return context; + } + + ContextType WebLoader::GetPreferredContextType() const + { + return ContextType::OpenGL_ES; + } + + GLFunction WebLoader::LoadFunction(const char* name) const + { + /*GLFunction func = reinterpret_cast(m_WebLib.GetSymbol(name)); + if (!func && WebGetProcAddress) + func = reinterpret_cast(WebGetProcAddress(name)); + + return func;*/ + + return reinterpret_cast(emscripten_webgl_get_proc_address(name)); + + //return nullptr; + } + + const char* WebLoader::TranslateError(EMSCRIPTEN_RESULT errorId) + { + switch (errorId) + { + case EMSCRIPTEN_RESULT_SUCCESS: return "The last function succeeded without error."; + case EMSCRIPTEN_RESULT_DEFERRED: return "The requested operation cannot be completed now for web security reasons, and has been deferred for completion in the next event handler."; + case EMSCRIPTEN_RESULT_NOT_SUPPORTED: return "The given operation is not supported by this browser or the target element. This value will be returned at the time the callback is registered if the operation is not supported."; + case EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED: return "The requested operation could not be completed now for web security reasons. It failed because the user requested the operation not be deferred."; + case EMSCRIPTEN_RESULT_INVALID_TARGET: return "The operation failed because the specified target element is invalid."; + case EMSCRIPTEN_RESULT_UNKNOWN_TARGET: return "The operation failed because the specified target element was not found."; + case EMSCRIPTEN_RESULT_INVALID_PARAM: return "The operation failed because an invalid parameter was passed to the function."; + case EMSCRIPTEN_RESULT_FAILED: return "Unknown error (EMSCRIPTEN_RESULT_FAILED)."; + case EMSCRIPTEN_RESULT_NO_DATA: return "The operation failed because no data is currently available."; + default: return "Invalid or unknown error."; + } + } + + bool WebLoader::ImplementFallback(const std::string_view& /*function*/) + { + return false; + } +} + +#if defined(NAZARA_PLATFORM_WINDOWS) +#include +#endif diff --git a/src/Nazara/Platform/SDL2/WindowImpl.cpp b/src/Nazara/Platform/SDL2/WindowImpl.cpp index a9f3c1e0f..0835c9d25 100644 --- a/src/Nazara/Platform/SDL2/WindowImpl.cpp +++ b/src/Nazara/Platform/SDL2/WindowImpl.cpp @@ -199,8 +199,10 @@ namespace Nz if (SDL_GetWindowWMInfo(m_handle, &wmInfo) != SDL_TRUE) { +#ifndef NAZARA_PLATFORM_WEB ErrorFlags flags(ErrorMode::ThrowException, true); NazaraError(std::string("failed to retrieve window manager info: ") + SDL_GetError()); +#endif } WindowHandle handle; @@ -244,8 +246,12 @@ namespace Nz #endif default: { +#if defined(NAZARA_PLATFORM_WEB) + handle.type = WindowBackend::Web; +#else ErrorFlags flags(ErrorMode::ThrowException, true); NazaraError("unhandled window subsystem"); +#endif } } diff --git a/src/Nazara/Renderer/Renderer.cpp b/src/Nazara/Renderer/Renderer.cpp index 57ecdffea..030b95ae9 100644 --- a/src/Nazara/Renderer/Renderer.cpp +++ b/src/Nazara/Renderer/Renderer.cpp @@ -23,6 +23,10 @@ #include +#ifdef NAZARA_PLATFORM_WEB +#include +#endif + #ifdef NAZARA_COMPILER_MSVC #define NazaraRendererPrefix "" #else @@ -77,6 +81,7 @@ namespace Nz void Renderer::LoadBackend(const Config& config) { +#ifndef NAZARA_PLATFORM_WEB constexpr std::array rendererPaths = { NazaraRendererPrefix "NazaraDirect3DRenderer" NazaraRendererDebugSuffix, // Direct3D NazaraRendererPrefix "NazaraMantleRenderer" NazaraRendererDebugSuffix, // Mantle @@ -201,6 +206,16 @@ namespace Nz m_rendererLib = std::move(chosenLib); #endif +#else + std::unique_ptr impl = std::make_unique(); + if (!impl || !impl->Prepare({})) + { + NazaraError("Failed to create renderer implementation"); + } + + m_rendererImpl = std::move(impl); +#endif + NazaraDebug("Using " + m_rendererImpl->QueryAPIString() + " as renderer"); } diff --git a/tests/GraphicsTest/main.cpp b/tests/GraphicsTest/main.cpp index 0dbb8580d..220ab6691 100644 --- a/tests/GraphicsTest/main.cpp +++ b/tests/GraphicsTest/main.cpp @@ -214,7 +214,7 @@ int main() if (!frame) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); - continue; + return; } framePipeline.GetDebugDrawer().DrawLine(Nz::Vector3f::Zero(), Nz::Vector3f::Forward(), Nz::Color::Blue()); @@ -234,7 +234,7 @@ int main() window.SetTitle(windowTitle + " - " + Nz::NumberToString(fps) + " FPS"); fps = 0; } - } + }); return EXIT_SUCCESS; } diff --git a/tests/GraphicsTest/xmake.lua b/tests/GraphicsTest/xmake.lua index b439049fd..76c04f596 100644 --- a/tests/GraphicsTest/xmake.lua +++ b/tests/GraphicsTest/xmake.lua @@ -1,3 +1,7 @@ target("GraphicsTest") add_deps("NazaraGraphics") - add_files("main.cpp") \ No newline at end of file + add_files("main.cpp") + + if is_plat("wasm") then + add_ldflags("--preload-file assets/", {force = true}) + end diff --git a/tests/RenderTest/main.cpp b/tests/RenderTest/main.cpp index fb49791e3..afc7031cf 100644 --- a/tests/RenderTest/main.cpp +++ b/tests/RenderTest/main.cpp @@ -287,7 +287,7 @@ int main() uboUpdate = true; }); - while (window.IsOpen()) + Nz::BasicMainloop(window, [&] { Nz::Window::ProcessEvents(); @@ -325,7 +325,7 @@ int main() if (!frame) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); - continue; + return; } debugDrawer.Reset(frame); @@ -414,7 +414,7 @@ int main() // Et on réinitialise le compteur de FPS fps = 0; } - } + }); return EXIT_SUCCESS; } diff --git a/tests/RenderTest/xmake.lua b/tests/RenderTest/xmake.lua index 1fa819c04..eec44e47d 100644 --- a/tests/RenderTest/xmake.lua +++ b/tests/RenderTest/xmake.lua @@ -1,3 +1,7 @@ target("RenderTest") add_deps("NazaraRenderer") - add_files("main.cpp") \ No newline at end of file + add_files("main.cpp") + + if is_plat("wasm") then + add_ldflags("--preload-file assets/examples/", {force = true}) + end diff --git a/xmake.lua b/xmake.lua index b6e1caefb..b4d44416b 100644 --- a/xmake.lua +++ b/xmake.lua @@ -15,6 +15,13 @@ local rendererBackends = { else remove_files("src/Nazara/OpenGLRenderer/Wrapper/Linux/**.cpp") end + + if is_plat("wasm") then + add_ldflags('-sFULL_ES2 -sFULL_ES3', { public = true }) + remove_files("src/Nazara/OpenGLRenderer/Wrapper/EGL/**.cpp") + else + remove_files("src/Nazara/OpenGLRenderer/Wrapper/Web/**.cpp") + end end }, VulkanRenderer = { @@ -38,6 +45,10 @@ local rendererBackends = { } NazaraRendererBackends = rendererBackends +if is_plat("wasm") then + rendererBackends.VulkanRenderer = nil +end + local modules = { Audio = { Deps = {"NazaraCore"}, @@ -88,7 +99,7 @@ local modules = { Network = { Deps = {"NazaraCore"}, Custom = function() - if is_plat("windows", "mingw") then + if is_plat("windows", "mingw") then add_syslinks("ws2_32") end @@ -118,6 +129,10 @@ local modules = { add_packages("libxext", "wayland", { links = {} }) -- we only need X11 headers elseif is_plat("macosx") then add_defines("SDL_VIDEO_DRIVER_COCOA=1") + add_packages("libx11", { links = {} }) -- we only need X11 headers + elseif is_plat("wasm") then + add_cxflags("-s USE_SDL=2") + add_ldflags("-s USE_SDL=2", { public = true }) end end }, @@ -136,7 +151,13 @@ local modules = { }, Utility = { Deps = {"NazaraCore"}, - Packages = {"entt", "freetype", "frozen", "ordered_map", "stb"} + Packages = {"entt", "freetype", "frozen", "ordered_map", "stb"}, + Custom = function() + if is_plat("wasm") then + add_cxflags("-s USE_FREETYPE=1") + add_ldflags("-s USE_FREETYPE=1", { public = true }) + end + end }, Widgets = { Deps = {"NazaraGraphics"}, @@ -155,6 +176,9 @@ if not has_config("embed_rendererbackends") then end end +add_ldflags("-g -s NO_DISABLE_EXCEPTION_CATCHING -s ALLOW_MEMORY_GROWTH=1") +add_cxflags("-g -s NO_DISABLE_EXCEPTION_CATCHING") + NazaraModules = modules includes("xmake/**.lua") @@ -170,15 +194,18 @@ option("unitybuild", { description = "Build the engine using unity build", defau set_project("NazaraEngine") set_xmakever("2.7.3") -add_requires("chipmunk2d", "dr_wav", "efsw", "entt v3.11.1", "fmt", "frozen", "kiwisolver", "libflac", "libsdl >=2.26.0", "minimp3", "ordered_map", "stb") -add_requires("freetype", { configs = { bzip2 = true, png = true, woff2 = true, zlib = true, debug = is_mode("debug") } }) -add_requires("libvorbis", { configs = { with_vorbisenc = false } }) -add_requires("openal-soft", { configs = { shared = true }}) -add_requires("newtondynamics3", { debug = is_plat("windows") and is_mode("debug") }) -- Newton doesn't like compiling in Debug on Linux +add_requires("dr_wav", "entt 3.10.1", "fmt", "frozen", "kiwisolver", "minimp3", "ordered_map", "stb") +if not is_plat("wasm") then + add_requires("chipmunk2d", "libsdl", "libflac", "efsw") + add_requires("freetype", { configs = { bzip2 = true, png = true, woff2 = true, zlib = true, debug = is_mode("debug") } }) + add_requires("libvorbis", { configs = { with_vorbisenc = false } }) + add_requires("openal-soft", { configs = { shared = true }}) + add_requires("newtondynamics", { debug = is_plat("windows") and is_mode("debug") }) -- Newton doesn't like compiling in Debug on Linux +end add_repositories("nazara-engine-repo https://github.com/NazaraEngine/xmake-repo") add_requires("nazarautils") -add_requires("nzsl", { debug = is_mode("debug"), configs = { with_symbols = not is_mode("release"), shared = true } }) +add_requires("nzsl", { debug = is_mode("debug"), configs = { with_symbols = not is_mode("release"), shared = not is_plat("wasm") } }) if is_plat("linux") then add_requires("libxext", "libuuid", "wayland") @@ -196,7 +223,7 @@ if has_config("tests") then add_rules("download.assets.unittests") end -set_allowedplats("windows", "mingw", "linux", "macosx") +set_allowedplats("windows", "mingw", "linux", "macosx", "wasm") set_allowedmodes("debug", "releasedbg", "release", "asan", "tsan", "coverage") set_defaultmode("debug") @@ -310,8 +337,8 @@ function ModuleTargetConfig(name, module) end end -for name, module in pairs(modules) do - target("Nazara" .. name, function () +function ModuleTarget(name, module) + target("Nazara" .. name) set_kind("shared") set_group("Modules")