From 565baee64be039cf745ed1d46c96dbe05228f3fb Mon Sep 17 00:00:00 2001 From: kgv Date: Wed, 16 Oct 2019 02:42:33 +0300 Subject: [PATCH] 0031070: Configuration - fix building issues when using Emscripten toolchain Handled __EMSCRIPTEN__ macros to: - Workaround atomics (__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4 is undefined, but GCC atomics are provided). - Suppress non-standard header warning. - Return OSD_LinuxREDHAT. - Avoid inclusion of XLib headers. - Skip fontconfig library. - Enable EGL+GLES path (translated by Emscripten into WebGL). - Skip eglCreatePbufferSurface() not implemented by Emscripten EGL. Fixed Graphic3d_Vec4.hxx usage within Quantity_ColorRGBA.hxx. OpenGl_ShaderManager::defaultGlslVersion() now prefers GLSL 300 es when WebGL 2.0 is available, as there no any OpenGL ES greater than 3.0 emulation so far. Shaders_Declarations.glsl - added workaround for GLSL compilation on WebGL 1.0 by defining Light properties accessors as macros instead of functions ('[]' : Index expression must be constant). OpenGl_FrameBuffer::Init() - added workaround for initialization of GL_DEPTH24_STENCIL8 depth-stencil attachment on WebGL 1.0 + GL_WEBGL_depth_texture extension. OpenGl_Context::Vec4FromQuantityColor() now considers myIsSRgbActive flag to handle use case, when Immediate Layer is drawn directly into window buffer, which is not sRGB-ready. Added new sample - OCCT WebGL viewer. --- dox/FILES_HTML.txt | 1 + dox/overview/images/sample_webgl.png | Bin 0 -> 75733 bytes dox/overview/overview.md | 9 + samples/webgl/.gitignore | 2 + samples/webgl/CMakeLists.txt | 66 ++ samples/webgl/ReadMe.md | 28 + samples/webgl/WasmOcctView.cpp | 678 ++++++++++++++++++ samples/webgl/WasmOcctView.h | 157 ++++ samples/webgl/WasmVKeys.h | 264 +++++++ samples/webgl/main.cpp | 66 ++ samples/webgl/occt-webgl-sample.html | 133 ++++ src/Aspect/Aspect_DisplayConnection.cxx | 6 +- src/Aspect/Aspect_DisplayConnection.hxx | 4 +- src/Aspect/Aspect_FBConfig.hxx | 2 +- src/Aspect/Aspect_XWD.hxx | 2 +- src/Font/Font_FontMgr.cxx | 8 +- src/InterfaceGraphic/InterfaceGraphic.hxx | 2 +- src/OSD/OSD_Chronometer.cxx | 2 +- src/OSD/OSD_MemInfo.cxx | 14 +- src/OSD/OSD_Path.cxx | 2 + src/OSD/OSD_signal.cxx | 2 +- src/OpenGl/OpenGl_Context.cxx | 44 ++ src/OpenGl/OpenGl_Context.hxx | 2 +- src/OpenGl/OpenGl_FrameBuffer.cxx | 105 ++- src/OpenGl/OpenGl_GlFunctions.hxx | 5 +- src/OpenGl/OpenGl_GraphicDriver.cxx | 24 +- src/OpenGl/OpenGl_GraphicDriver.hxx | 6 +- src/OpenGl/OpenGl_ShaderManager.cxx | 9 + src/OpenGl/OpenGl_Window.cxx | 2 + src/Quantity/Quantity_ColorRGBA.cxx | 4 +- src/Shaders/Declarations.glsl | 42 +- src/Shaders/DeclarationsImpl.glsl | 12 - src/Shaders/Shaders_DeclarationsImpl_glsl.pxx | 12 - src/Shaders/Shaders_Declarations_glsl.pxx | 42 +- src/Standard/Standard_Atomic.hxx | 2 +- src/Xw/Xw_Window.cxx | 2 +- src/Xw/Xw_Window.hxx | 2 +- 37 files changed, 1639 insertions(+), 124 deletions(-) create mode 100644 dox/overview/images/sample_webgl.png create mode 100644 samples/webgl/.gitignore create mode 100644 samples/webgl/CMakeLists.txt create mode 100644 samples/webgl/ReadMe.md create mode 100644 samples/webgl/WasmOcctView.cpp create mode 100644 samples/webgl/WasmOcctView.h create mode 100644 samples/webgl/WasmVKeys.h create mode 100644 samples/webgl/main.cpp create mode 100644 samples/webgl/occt-webgl-sample.html diff --git a/dox/FILES_HTML.txt b/dox/FILES_HTML.txt index 2c1be0ed01..2accb6670a 100644 --- a/dox/FILES_HTML.txt +++ b/dox/FILES_HTML.txt @@ -14,6 +14,7 @@ overview/overview.md ../samples/qt/AndroidQt/ReadMe.md ../samples/java/jniviewer/ReadMe.md ../samples/ios/UIKitSample/ReadMe.md +../samples/webgl/ReadMe.md tutorial/tutorial.md diff --git a/dox/overview/images/sample_webgl.png b/dox/overview/images/sample_webgl.png new file mode 100644 index 0000000000000000000000000000000000000000..7d3781faad88696c45c7dd093a92002ce3e40ad7 GIT binary patch literal 75733 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%0Dy^Z;~k)p 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 CusF5= literal 0 HcmV?d00001 diff --git a/dox/overview/overview.md b/dox/overview/overview.md index 289c0e6d0d..6096a2d5b3 100644 --- a/dox/overview/overview.md +++ b/dox/overview/overview.md @@ -214,6 +214,7 @@ for which OCCT is certified to work. | Linux | GNU gcc 4.3+
LLVM CLang 3.6+ | | OS X / macOS | XCode 6 or newer | | Android | NDK r10, GNU gcc 4.8 or newer | +| Web | Emscripten SDK 1.39 or newer (CLang) | 1) VC++ 141 64-bit is used for regular testing and for building binary package of official release of OCCT on Windows. @@ -572,3 +573,11 @@ There is a sample demonstrating usage of OCCT on iOS with Apple UIKit framework. @figure{/overview/images/sample_ios_uikit.png} See \subpage occt_samples_ios_uikit "iOS sample Readme" for details. + +@subsubsection OCCT_OVW_SECTION_7_3_6 Web + +WebGL Viewer sample demonstrating usage of OCCT 3D Viewer in Web browser with Emscripten SDK can be found in `samples/webgl`. + +@figure{/overview/images/sample_webgl.png} + +See \subpage occt_samples_webgl "WebGL sample Readme" for details. diff --git a/samples/webgl/.gitignore b/samples/webgl/.gitignore new file mode 100644 index 0000000000..ecdfdf7c9c --- /dev/null +++ b/samples/webgl/.gitignore @@ -0,0 +1,2 @@ +/build +/work diff --git a/samples/webgl/CMakeLists.txt b/samples/webgl/CMakeLists.txt new file mode 100644 index 0000000000..ee35d9d828 --- /dev/null +++ b/samples/webgl/CMakeLists.txt @@ -0,0 +1,66 @@ +cmake_minimum_required(VERSION 3.2) + +project(occt-webgl-sample) + +set(CMAKE_CXX_STANDARD 11) +set(APP_VERSION_MAJOR 1) +set(APP_VERSION_MINOR 0) +set(APP_TARGET occt-webgl-sample) + +# customize build +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s WASM=1") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s USE_WEBGL2=1") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s ALLOW_MEMORY_GROWTH=1") +#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s SAFE_HEAP=1") +#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s NO_EXIT_RUNTIME=1") +#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s TOTAL_MEMORY=16MB") +#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s ABORTING_MALLOC=0") +#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s FORCE_FILESYSTEM=1") +#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --preload-file myFile") + +INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}) +file(GLOB SOURCES + *.h + *.cpp +) +source_group ("Headers" FILES + WasmOcctView.h) +source_group ("Sources" FILES + WasmOcctView.cpp + main.cpp) + +# FreeType +find_package(freetype REQUIRED NO_DEFAULT_PATH) +if(freetype_FOUND) + message (STATUS "Using FreeType from \"${freetype_DIR}\"" ) +else() + message(WARNING "Could not find FreeType, please set freetype_DIR variable." ) +endif() + +# Open CASCADE Technology +find_package(OpenCASCADE REQUIRED NO_DEFAULT_PATH) +if(OpenCASCADE_FOUND) + message (STATUS "Using OpenCASCADE from \"${OpenCASCADE_DIR}\"" ) + INCLUDE_DIRECTORIES(${OpenCASCADE_INCLUDE_DIR}) + LINK_DIRECTORIES(${OpenCASCADE_LIBRARY_DIR}) +else() + message(WARNING "Could not find OpenCASCADE, please set OpenCASCADE_DIR variable." ) + set(OCCT_LIBRARY_DIR) + set(OCCT_BIN_DIR) +endif() + +set(OpenCASCADE_LIBS TKRWMesh TKBinXCAF TKBin TKBinL TKOpenGl TKXCAF TKVCAF TKCAF TKV3d TKHLR TKMesh TKService TKShHealing TKPrim TKTopAlgo TKGeomAlgo TKBRep TKGeomBase TKG3d TKG2d TKMath TKLCAF TKCDF TKernel) + +add_executable(${APP_TARGET} ${SOURCES}) +target_link_libraries( + ${APP_TARGET} + ${OpenCASCADE_LIBS} + freetype +) +set_target_properties(${APP_TARGET} PROPERTIES LINK_FLAGS "-s EXPORTED_FUNCTIONS=['_main','_onFileDataRead'] -s EXTRA_EXPORTED_RUNTIME_METHODS=['ccall','cwrap']") + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" LIBRARY DESTINATION "${CMAKE_INSTALL_PREFIX}") +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.wasm DESTINATION ${CMAKE_INSTALL_PREFIX}) +install(FILES occt-webgl-sample.html DESTINATION ${CMAKE_INSTALL_PREFIX}) +install(FILES ${OpenCASCADE_RESOURCE_DIR}/DrawResources/OCC_logo.png DESTINATION ${CMAKE_INSTALL_PREFIX}) +install(FILES ${OpenCASCADE_RESOURCE_DIR}/DrawResources/lamp.ico DESTINATION ${CMAKE_INSTALL_PREFIX}) diff --git a/samples/webgl/ReadMe.md b/samples/webgl/ReadMe.md new file mode 100644 index 0000000000..7e748eb026 --- /dev/null +++ b/samples/webgl/ReadMe.md @@ -0,0 +1,28 @@ +OCCT WebGL Viewer sample {#occt_samples_webgl} +================== + +This sample demonstrates simple way of using OCCT libraries in Web application written in C++ and translated into WebAssembly module using Emscripten SDK (emsdk): +https://emscripten.org/ + +Sample consists of the Open CASCADE 3D Viewer with a button for opening a model in BREP format. +The sample requires a WebGL 2.0 capable browser supporting WebAssembly 1.0 (Wasm). + +Installation and configuration: + 1. Install Emscripten SDK and activate minimal configuration (Python, Java and CLang) following *emsdk* documentation. Activate also MinGW when building sample on Windows host. + 2. Build (using *emsdk*) or download FreeType static library. + 3. Configure CMake for building Open CASCADE Technology (OCCT) static libraries (BUILD_LIBRARY_TYPE="Static"). + For this, activate *emsdk* command prompt, configure CMake for building OCCT using cross-compilation toolchain, disable *BUILD_MODULE_Draw*. + 4. Perform building and installation steps. +~~~~~ + > ${EMSDK}/fastcomp/emscripten/cmake/Modules/Platform/Emscripten.cmake +~~~~~ + 5. Configure CMake for building this WebGL sample using *emsdk* with paths to OCCT and FreeType. Perform building and installation steps. + 6. Copy data/occ/Ball.brep from OCCT into "samples" folder within WebGL sample installation path. + 7. Navigate to installation folder and start web server from it; Python coming with *emsdk* can be used for this purpose: +~~~~~ + > python -m SimpleHTTPServer 8080 +~~~~~ + 8. Open compatible browser and enter path taking into account your web server settings: +~~~~~ + > http://localhost:8080/occt-webgl-sample.html +~~~~~ diff --git a/samples/webgl/WasmOcctView.cpp b/samples/webgl/WasmOcctView.cpp new file mode 100644 index 0000000000..68a8b86bba --- /dev/null +++ b/samples/webgl/WasmOcctView.cpp @@ -0,0 +1,678 @@ +// Copyright (c) 2019 OPEN CASCADE SAS +// +// This file is part of the examples of the Open CASCADE Technology software library. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE + +#include "WasmOcctView.h" + +#include "WasmVKeys.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define THE_CANVAS_ID "canvas" + +namespace +{ + EM_JS(int, jsCanvasGetWidth, (), { + return canvas.width; + }); + + EM_JS(int, jsCanvasGetHeight, (), { + return canvas.height; + }); + + EM_JS(float, jsDevicePixelRatio, (), { + var aDevicePixelRatio = window.devicePixelRatio || 1; + return aDevicePixelRatio; + }); + + //! Return cavas size in pixels. + static Graphic3d_Vec2i jsCanvasSize() + { + return Graphic3d_Vec2i (jsCanvasGetWidth(), jsCanvasGetHeight()); + } +} + +// ================================================================ +// Function : WasmOcctView +// Purpose : +// ================================================================ +WasmOcctView::WasmOcctView() +: myDevicePixelRatio (1.0f), + myUpdateRequests (0) +{ +} + +// ================================================================ +// Function : ~WasmOcctView +// Purpose : +// ================================================================ +WasmOcctView::~WasmOcctView() +{ +} + +// ================================================================ +// Function : run +// Purpose : +// ================================================================ +void WasmOcctView::run() +{ + initWindow(); + initViewer(); + initDemoScene(); + if (myView.IsNull()) + { + return; + } + + myView->MustBeResized(); + myView->Redraw(); + + // There is no inifinite message loop, main() will return from here immediately. + // Tell that our Module should be left loaded and handle events through callbacks. + //emscripten_set_main_loop (redrawView, 60, 1); + //emscripten_set_main_loop (redrawView, -1, 1); + EM_ASM(Module['noExitRuntime'] = true); +} + +// ================================================================ +// Function : initWindow +// Purpose : +// ================================================================ +void WasmOcctView::initWindow() +{ + myDevicePixelRatio = jsDevicePixelRatio(); + myCanvasId = THE_CANVAS_ID; + const char* aTargetId = !myCanvasId.IsEmpty() ? myCanvasId.ToCString() : NULL; + const EM_BOOL toUseCapture = EM_TRUE; + emscripten_set_resize_callback (NULL, this, toUseCapture, onResizeCallback); + + emscripten_set_mousedown_callback (NULL, this, toUseCapture, onMouseCallback); + emscripten_set_mouseup_callback (NULL, this, toUseCapture, onMouseCallback); + emscripten_set_mousemove_callback (NULL, this, toUseCapture, onMouseCallback); + emscripten_set_dblclick_callback (aTargetId, this, toUseCapture, onMouseCallback); + emscripten_set_click_callback (aTargetId, this, toUseCapture, onMouseCallback); + emscripten_set_mouseenter_callback (aTargetId, this, toUseCapture, onMouseCallback); + emscripten_set_mouseleave_callback (aTargetId, this, toUseCapture, onMouseCallback); + emscripten_set_wheel_callback (aTargetId, this, toUseCapture, onWheelCallback); + + emscripten_set_touchstart_callback (aTargetId, this, toUseCapture, onTouchCallback); + emscripten_set_touchend_callback (aTargetId, this, toUseCapture, onTouchCallback); + emscripten_set_touchmove_callback (aTargetId, this, toUseCapture, onTouchCallback); + emscripten_set_touchcancel_callback(aTargetId, this, toUseCapture, onTouchCallback); + + //emscripten_set_keypress_callback (NULL, this, toUseCapture, onKeyCallback); + emscripten_set_keydown_callback (NULL, this, toUseCapture, onKeyDownCallback); + emscripten_set_keyup_callback (NULL, this, toUseCapture, onKeyUpCallback); +} + +// ================================================================ +// Function : dumpGlInfo +// Purpose : +// ================================================================ +void WasmOcctView::dumpGlInfo (bool theIsBasic) +{ + TColStd_IndexedDataMapOfStringString aGlCapsDict; + myView->DiagnosticInformation (aGlCapsDict, theIsBasic ? Graphic3d_DiagnosticInfo_Basic : Graphic3d_DiagnosticInfo_Complete); + if (theIsBasic) + { + TCollection_AsciiString aViewport; + aGlCapsDict.FindFromKey ("Viewport", aViewport); + aGlCapsDict.Clear(); + aGlCapsDict.Add ("Viewport", aViewport); + } + aGlCapsDict.Add ("Display scale", TCollection_AsciiString(myDevicePixelRatio)); + + // beautify output + { + TCollection_AsciiString* aGlVer = aGlCapsDict.ChangeSeek ("GLversion"); + TCollection_AsciiString* aGlslVer = aGlCapsDict.ChangeSeek ("GLSLversion"); + if (aGlVer != NULL + && aGlslVer != NULL) + { + *aGlVer = *aGlVer + " [GLSL: " + *aGlslVer + "]"; + aGlslVer->Clear(); + } + } + + TCollection_AsciiString anInfo; + for (TColStd_IndexedDataMapOfStringString::Iterator aValueIter (aGlCapsDict); aValueIter.More(); aValueIter.Next()) + { + if (!aValueIter.Value().IsEmpty()) + { + if (!anInfo.IsEmpty()) + { + anInfo += "\n"; + } + anInfo += aValueIter.Key() + ": " + aValueIter.Value(); + } + } + + ::Message::DefaultMessenger()->Send (anInfo, Message_Warning); +} + +// ================================================================ +// Function : initPixelScaleRatio +// Purpose : +// ================================================================ +void WasmOcctView::initPixelScaleRatio() +{ + SetTouchToleranceScale (myDevicePixelRatio); + if (!myView.IsNull()) + { + myView->ChangeRenderingParams().Resolution = (unsigned int )(96.0 * myDevicePixelRatio + 0.5); + } + if (!myContext.IsNull()) + { + myContext->SetPixelTolerance (int(myDevicePixelRatio * 6.0)); + if (!myViewCube.IsNull()) + { + static const double THE_CUBE_SIZE = 60.0; + myViewCube->SetSize (myDevicePixelRatio * THE_CUBE_SIZE, false); + myViewCube->SetBoxFacetExtension (myViewCube->Size() * 0.15); + myViewCube->SetAxesPadding (myViewCube->Size() * 0.10); + myViewCube->SetFontHeight (THE_CUBE_SIZE * 0.16); + if (myViewCube->HasInteractiveContext()) + { + myContext->Redisplay (myViewCube, false); + } + } + } +} + +// ================================================================ +// Function : initViewer +// Purpose : +// ================================================================ +bool WasmOcctView::initViewer() +{ + // Build with "--preload-file MyFontFile.ttf" option + // and register font in Font Manager to use custom font(s). + /*const char* aFontPath = "MyFontFile.ttf"; + if (Handle(Font_SystemFont) aFont = Font_FontMgr::GetInstance()->CheckFont (aFontPath)) + { + Font_FontMgr::GetInstance()->RegisterFont (aFont, true); + } + else + { + Message::DefaultMessenger()->Send (TCollection_AsciiString ("Error: font '") + aFontPath + "' is not found", Message_Fail); + }*/ + + Handle(Aspect_DisplayConnection) aDisp; + Handle(OpenGl_GraphicDriver) aDriver = new OpenGl_GraphicDriver (aDisp, false); + aDriver->ChangeOptions().buffersNoSwap = true; // swap has no effect in WebGL + if (!aDriver->InitContext()) + { + Message::DefaultMessenger()->Send (TCollection_AsciiString ("Error: EGL initialization failed"), Message_Fail); + return false; + } + + Handle(V3d_Viewer) aViewer = new V3d_Viewer (aDriver); + aViewer->SetComputedMode (false); + aViewer->SetDefaultShadingModel (Graphic3d_TOSM_FRAGMENT); + aViewer->SetDefaultLights(); + aViewer->SetLightOn(); + + Handle(Aspect_NeutralWindow) aWindow = new Aspect_NeutralWindow(); + Graphic3d_Vec2i aWinSize = jsCanvasSize(); + if (aWinSize.x() < 10 || aWinSize.y() < 10) + { + Message::DefaultMessenger()->Send (TCollection_AsciiString ("Warning: invalid canvas size"), Message_Warning); + } + aWindow->SetSize (aWinSize.x(), aWinSize.y()); + + myTextStyle = new Prs3d_TextAspect(); + myTextStyle->SetFont (Font_NOF_ASCII_MONO); + myTextStyle->SetHeight (12); + myTextStyle->Aspect()->SetColor (Quantity_NOC_GRAY95); + myTextStyle->Aspect()->SetColorSubTitle (Quantity_NOC_BLACK); + myTextStyle->Aspect()->SetDisplayType (Aspect_TODT_SHADOW); + myTextStyle->Aspect()->SetTextFontAspect (Font_FA_Bold); + myTextStyle->Aspect()->SetTextZoomable (false); + myTextStyle->SetHorizontalJustification (Graphic3d_HTA_LEFT); + myTextStyle->SetVerticalJustification (Graphic3d_VTA_BOTTOM); + + myView = new V3d_View (aViewer); + myView->SetImmediateUpdate (false); + myView->ChangeRenderingParams().Resolution = (unsigned int )(96.0 * myDevicePixelRatio + 0.5); + myView->ChangeRenderingParams().ToShowStats = true; + myView->ChangeRenderingParams().StatsTextAspect = myTextStyle->Aspect(); + myView->ChangeRenderingParams().StatsTextHeight = (int )myTextStyle->Height(); + myView->SetWindow (aWindow); + dumpGlInfo (false); + + myContext = new AIS_InteractiveContext (aViewer); + initPixelScaleRatio(); + return true; +} + +// ================================================================ +// Function : initDemoScene +// Purpose : +// ================================================================ +void WasmOcctView::initDemoScene() +{ + if (myContext.IsNull()) + { + return; + } + + //myView->TriedronDisplay (Aspect_TOTP_LEFT_LOWER, Quantity_NOC_GOLD, 0.08, V3d_WIREFRAME); + + myViewCube = new AIS_ViewCube(); + // presentation parameters + initPixelScaleRatio(); + myViewCube->SetTransformPersistence (new Graphic3d_TransformPers (Graphic3d_TMF_TriedronPers, Aspect_TOTP_RIGHT_LOWER, Graphic3d_Vec2i (100, 100))); + myViewCube->Attributes()->SetDatumAspect (new Prs3d_DatumAspect()); + myViewCube->Attributes()->DatumAspect()->SetTextAspect (myTextStyle); + // animation parameters + myViewCube->SetViewAnimation (myViewAnimation); + myViewCube->SetFixedAnimationLoop (false); + myViewCube->SetAutoStartAnimation (true); + myContext->Display (myViewCube, false); + + // Build with "--preload-file MySampleFile.brep" option to load some shapes here. +} + + +// ================================================================ +// Function : updateView +// Purpose : +// ================================================================ +void WasmOcctView::updateView() +{ + if (!myView.IsNull()) + { + if (++myUpdateRequests == 1) + { + emscripten_async_call (onRedrawView, this, 0); + } + } +} + +// ================================================================ +// Function : redrawView +// Purpose : +// ================================================================ +void WasmOcctView::redrawView() +{ + if (!myView.IsNull()) + { + FlushViewEvents (myContext, myView, true); + } +} + +// ================================================================ +// Function : handleViewRedraw +// Purpose : +// ================================================================ +void WasmOcctView::handleViewRedraw (const Handle(AIS_InteractiveContext)& theCtx, + const Handle(V3d_View)& theView) +{ + myUpdateRequests = 0; + AIS_ViewController::handleViewRedraw (theCtx, theView); + if (myToAskNextFrame) + { + // ask more frames + ++myUpdateRequests; + emscripten_async_call (onRedrawView, this, 0); + } +} + +// ================================================================ +// Function : onResizeEvent +// Purpose : +// ================================================================ +EM_BOOL WasmOcctView::onResizeEvent (int theEventType, const EmscriptenUiEvent* theEvent) +{ + (void )theEventType; // EMSCRIPTEN_EVENT_RESIZE or EMSCRIPTEN_EVENT_CANVASRESIZED + (void )theEvent; + if (myView.IsNull()) + { + return EM_FALSE; + } + + Handle(Aspect_NeutralWindow) aWindow = Handle(Aspect_NeutralWindow)::DownCast (myView->Window()); + Graphic3d_Vec2i aWinSizeOld, aWinSizeNew (jsCanvasSize()); + if (aWinSizeNew.x() < 10 || aWinSizeNew.y() < 10) + { + Message::DefaultMessenger()->Send (TCollection_AsciiString ("Warning: invalid canvas size"), Message_Warning); + } + aWindow->Size (aWinSizeOld.x(), aWinSizeOld.y()); + const float aPixelRatio = jsDevicePixelRatio(); + if (aWinSizeNew != aWinSizeOld + || aPixelRatio != myDevicePixelRatio) + { + if (myDevicePixelRatio != aPixelRatio) + { + myDevicePixelRatio = aPixelRatio; + initPixelScaleRatio(); + } + aWindow->SetSize (aWinSizeNew.x(), aWinSizeNew.y()); + myView->MustBeResized(); + myView->Invalidate(); + myView->Redraw(); + dumpGlInfo (true); + } + return EM_TRUE; +} + +// ================================================================ +// Function : onMouseEvent +// Purpose : +// ================================================================ +EM_BOOL WasmOcctView::onMouseEvent (int theEventType, const EmscriptenMouseEvent* theEvent) +{ + if (myView.IsNull()) + { + return EM_FALSE; + } + + Graphic3d_Vec2i aWinSize; + myView->Window()->Size (aWinSize.x(), aWinSize.y()); + const Graphic3d_Vec2i aNewPos = convertPointToBacking (Graphic3d_Vec2i (theEvent->canvasX, theEvent->canvasY)); + Aspect_VKeyFlags aFlags = 0; + if (theEvent->ctrlKey == EM_TRUE) { aFlags |= Aspect_VKeyFlags_CTRL; } + if (theEvent->shiftKey == EM_TRUE) { aFlags |= Aspect_VKeyFlags_SHIFT; } + if (theEvent->altKey == EM_TRUE) { aFlags |= Aspect_VKeyFlags_ALT; } + if (theEvent->metaKey == EM_TRUE) { aFlags |= Aspect_VKeyFlags_META; } + + const bool isEmulated = false; + const Aspect_VKeyMouse aButtons = WasmVKeys_MouseButtonsFromNative (theEvent->buttons); + switch (theEventType) + { + case EMSCRIPTEN_EVENT_MOUSEMOVE: + { + if ((aNewPos.x() < 0 || aNewPos.x() > aWinSize.x() + || aNewPos.y() < 0 || aNewPos.y() > aWinSize.y()) + && PressedMouseButtons() == Aspect_VKeyMouse_NONE) + { + return EM_FALSE; + } + if (UpdateMousePosition (aNewPos, aButtons, aFlags, isEmulated)) + { + updateView(); + } + break; + } + case EMSCRIPTEN_EVENT_MOUSEDOWN: + case EMSCRIPTEN_EVENT_MOUSEUP: + { + if (aNewPos.x() < 0 || aNewPos.x() > aWinSize.x() + || aNewPos.y() < 0 || aNewPos.y() > aWinSize.y()) + { + return EM_FALSE; + } + if (UpdateMouseButtons (aNewPos, aButtons, aFlags, isEmulated)) + { + updateView(); + } + break; + } + case EMSCRIPTEN_EVENT_CLICK: + case EMSCRIPTEN_EVENT_DBLCLICK: + { + if (aNewPos.x() < 0 || aNewPos.x() > aWinSize.x() + || aNewPos.y() < 0 || aNewPos.y() > aWinSize.y()) + { + return EM_FALSE; + } + break; + } + case EMSCRIPTEN_EVENT_MOUSEENTER: + { + break; + } + case EMSCRIPTEN_EVENT_MOUSELEAVE: + { + // there is no SetCapture() support, so that mouse unclick events outside canvas will not arrive, + // so we have to forget current state... + if (UpdateMouseButtons (aNewPos, Aspect_VKeyMouse_NONE, aFlags, isEmulated)) + { + updateView(); + } + break; + } + } + return EM_TRUE; +} + +// ================================================================ +// Function : onWheelEvent +// Purpose : +// ================================================================ +EM_BOOL WasmOcctView::onWheelEvent (int theEventType, const EmscriptenWheelEvent* theEvent) +{ + if (myView.IsNull() + || theEventType != EMSCRIPTEN_EVENT_WHEEL) + { + return EM_FALSE; + } + + Graphic3d_Vec2i aWinSize; + myView->Window()->Size (aWinSize.x(), aWinSize.y()); + const Graphic3d_Vec2i aNewPos = convertPointToBacking (Graphic3d_Vec2i (theEvent->mouse.canvasX, theEvent->mouse.canvasY)); + if (aNewPos.x() < 0 || aNewPos.x() > aWinSize.x() + || aNewPos.y() < 0 || aNewPos.y() > aWinSize.y()) + { + return EM_FALSE; + } + + double aDelta = 0.0; + switch (theEvent->deltaMode) + { + case DOM_DELTA_PIXEL: + { + aDelta = theEvent->deltaY / (5.0 * myDevicePixelRatio); + break; + } + case DOM_DELTA_LINE: + { + aDelta = theEvent->deltaY * 8.0; + break; + } + case DOM_DELTA_PAGE: + { + aDelta = theEvent->deltaY >= 0.0 ? 24.0 : -24.0; + break; + } + } + + if (UpdateZoom (Aspect_ScrollDelta (aNewPos, -aDelta))) + { + updateView(); + } + return EM_TRUE; +} + +// ================================================================ +// Function : onTouchEvent +// Purpose : +// ================================================================ +EM_BOOL WasmOcctView::onTouchEvent (int theEventType, const EmscriptenTouchEvent* theEvent) +{ + const double aClickTolerance = 5.0; + if (myView.IsNull()) + { + return EM_FALSE; + } + + Graphic3d_Vec2i aWinSize; + myView->Window()->Size (aWinSize.x(), aWinSize.y()); + bool hasUpdates = false; + for (int aTouchIter = 0; aTouchIter < theEvent->numTouches; ++aTouchIter) + { + const EmscriptenTouchPoint& aTouch = theEvent->touches[aTouchIter]; + if (!aTouch.isChanged) + { + continue; + } + + const Standard_Size aTouchId = (Standard_Size )aTouch.identifier; + const Graphic3d_Vec2i aNewPos = convertPointToBacking (Graphic3d_Vec2i (aTouch.canvasX, aTouch.canvasY)); + switch (theEventType) + { + case EMSCRIPTEN_EVENT_TOUCHSTART: + { + if (aNewPos.x() >= 0 && aNewPos.x() < aWinSize.x() + && aNewPos.y() >= 0 && aNewPos.y() < aWinSize.y()) + { + hasUpdates = true; + AddTouchPoint (aTouchId, Graphic3d_Vec2d (aNewPos)); + myClickTouch.From.SetValues (-1.0, -1.0); + if (myTouchPoints.Extent() == 1) + { + myClickTouch.From = Graphic3d_Vec2d (aNewPos); + } + } + break; + } + case EMSCRIPTEN_EVENT_TOUCHMOVE: + { + const int anOldIndex = myTouchPoints.FindIndex (aTouchId); + if (anOldIndex != 0) + { + hasUpdates = true; + UpdateTouchPoint (aTouchId, Graphic3d_Vec2d (aNewPos)); + if (myTouchPoints.Extent() == 1 + && (myClickTouch.From - Graphic3d_Vec2d (aNewPos)).cwiseAbs().maxComp() > aClickTolerance) + { + myClickTouch.From.SetValues (-1.0, -1.0); + } + } + break; + } + case EMSCRIPTEN_EVENT_TOUCHEND: + case EMSCRIPTEN_EVENT_TOUCHCANCEL: + { + if (RemoveTouchPoint (aTouchId)) + { + if (myTouchPoints.IsEmpty() + && myClickTouch.From.minComp() >= 0.0) + { + if (myDoubleTapTimer.IsStarted() + && myDoubleTapTimer.ElapsedTime() <= myMouseDoubleClickInt) + { + myView->FitAll (0.01, false); + myView->Invalidate(); + } + else + { + myDoubleTapTimer.Stop(); + myDoubleTapTimer.Reset(); + myDoubleTapTimer.Start(); + SelectInViewer (Graphic3d_Vec2i (myClickTouch.From), false); + } + } + hasUpdates = true; + } + break; + } + } + } + if (hasUpdates) + { + updateView(); + } + return hasUpdates || !myTouchPoints.IsEmpty() ? EM_TRUE : EM_FALSE; +} + +// ================================================================ +// Function : onKeyDownEvent +// Purpose : +// ================================================================ +EM_BOOL WasmOcctView::onKeyDownEvent (int theEventType, const EmscriptenKeyboardEvent* theEvent) +{ + if (myView.IsNull() + || theEventType != EMSCRIPTEN_EVENT_KEYDOWN) // EMSCRIPTEN_EVENT_KEYPRESS + { + return EM_FALSE; + } + + const double aTimeStamp = EventTime(); + const Aspect_VKey aVKey = WasmVKeys_VirtualKeyFromNative (theEvent->keyCode); + if (aVKey == Aspect_VKey_UNKNOWN) + { + return EM_FALSE; + } + + if (theEvent->repeat == EM_FALSE) + { + myKeys.KeyDown (aVKey, aTimeStamp); + } + + if (Aspect_VKey2Modifier (aVKey) == 0) + { + // normal key + } + return EM_FALSE; +} + +// ================================================================ +// Function : onKeyUpEvent +// Purpose : +// ================================================================ +EM_BOOL WasmOcctView::onKeyUpEvent (int theEventType, const EmscriptenKeyboardEvent* theEvent) +{ + if (myView.IsNull() + || theEventType != EMSCRIPTEN_EVENT_KEYUP) + { + return EM_FALSE; + } + + const double aTimeStamp = EventTime(); + const Aspect_VKey aVKey = WasmVKeys_VirtualKeyFromNative (theEvent->keyCode); + if (aVKey == Aspect_VKey_UNKNOWN) + { + return EM_FALSE; + } + + if (theEvent->repeat == EM_TRUE) + { + return EM_FALSE; + } + + const unsigned int aModif = myKeys.Modifiers(); + myKeys.KeyUp (aVKey, aTimeStamp); + if (Aspect_VKey2Modifier (aVKey) == 0) + { + // normal key released + switch (aVKey | aModif) + { + case Aspect_VKey_F: + { + myView->FitAll (0.01, false); + myView->Invalidate(); + updateView(); + return EM_TRUE; + } + } + } + return EM_FALSE; +} diff --git a/samples/webgl/WasmOcctView.h b/samples/webgl/WasmOcctView.h new file mode 100644 index 0000000000..0715e7cbac --- /dev/null +++ b/samples/webgl/WasmOcctView.h @@ -0,0 +1,157 @@ +// Copyright (c) 2019 OPEN CASCADE SAS +// +// This file is part of the examples of the Open CASCADE Technology software library. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE + +#ifndef _WasmOcctView_HeaderFile +#define _WasmOcctView_HeaderFile + +#include +#include +#include + +#include +#include + +class AIS_ViewCube; + +//! Sample class creating 3D Viewer within Emscripten canvas. +class WasmOcctView : protected AIS_ViewController +{ +public: + //! Default constructor. + WasmOcctView(); + + //! Destructor. + virtual ~WasmOcctView(); + + //! Main application entry point. + void run(); + + //! Return interactive context. + const Handle(AIS_InteractiveContext)& Context() const { return myContext; } + + //! Return view. + const Handle(V3d_View)& View() const { return myView; } + + //! Return device pixel ratio for handling high DPI displays. + float DevicePixelRatio() const { return myDevicePixelRatio; } + +private: + + //! Create window. + void initWindow(); + + //! Create 3D Viewer. + bool initViewer(); + + //! Fill 3D Viewer with a DEMO items. + void initDemoScene(); + + //! Application event loop. + void mainloop(); + + //! Request view redrawing. + void updateView(); + + //! Flush events and redraw view. + void redrawView(); + + //! Handle view redraw. + virtual void handleViewRedraw (const Handle(AIS_InteractiveContext)& theCtx, + const Handle(V3d_View)& theView) override; + + //! Dump WebGL context information. + void dumpGlInfo (bool theIsBasic); + + //! Initialize pixel scale ratio. + void initPixelScaleRatio(); + + //! Return point from logical units to backing store. + Graphic3d_Vec2d convertPointToBacking (const Graphic3d_Vec2d& thePnt) const + { + return thePnt * myDevicePixelRatio; + } + + //! Return point from logical units to backing store. + Graphic3d_Vec2i convertPointToBacking (const Graphic3d_Vec2i& thePnt) const + { + Graphic3d_Vec2d aPnt = Graphic3d_Vec2d (thePnt) * myDevicePixelRatio + Graphic3d_Vec2d (0.5); + return Graphic3d_Vec2i (aPnt); + } + +//! @name Emscripten callbacks +private: + //! Window resize event. + EM_BOOL onResizeEvent (int theEventType, const EmscriptenUiEvent* theEvent); + + //! Mouse event. + EM_BOOL onMouseEvent (int theEventType, const EmscriptenMouseEvent* theEvent); + + //! Scroll event. + EM_BOOL onWheelEvent (int theEventType, const EmscriptenWheelEvent* theEvent); + + //! Touch event. + EM_BOOL onTouchEvent (int theEventType, const EmscriptenTouchEvent* theEvent); + + //! Key down event. + EM_BOOL onKeyDownEvent (int theEventType, const EmscriptenKeyboardEvent* theEvent); + + //! Key up event. + EM_BOOL onKeyUpEvent (int theEventType, const EmscriptenKeyboardEvent* theEvent); + +//! @name Emscripten callbacks (static functions) +private: + + static EM_BOOL onResizeCallback (int theEventType, const EmscriptenUiEvent* theEvent, void* theView) + { return ((WasmOcctView* )theView)->onResizeEvent (theEventType, theEvent); } + + static void onRedrawView (void* theView) + { return ((WasmOcctView* )theView)->redrawView(); } + + static EM_BOOL onMouseCallback (int theEventType, const EmscriptenMouseEvent* theEvent, void* theView) + { return ((WasmOcctView* )theView)->onMouseEvent (theEventType, theEvent); } + + static EM_BOOL onWheelCallback (int theEventType, const EmscriptenWheelEvent* theEvent, void* theView) + { return ((WasmOcctView* )theView)->onWheelEvent (theEventType, theEvent); } + + static EM_BOOL onTouchCallback (int theEventType, const EmscriptenTouchEvent* theEvent, void* theView) + { return ((WasmOcctView* )theView)->onTouchEvent (theEventType, theEvent); } + + static EM_BOOL onKeyDownCallback (int theEventType, const EmscriptenKeyboardEvent* theEvent, void* theView) + { return ((WasmOcctView* )theView)->onKeyDownEvent (theEventType, theEvent); } + + static EM_BOOL onKeyUpCallback (int theEventType, const EmscriptenKeyboardEvent* theEvent, void* theView) + { return ((WasmOcctView* )theView)->onKeyUpEvent (theEventType, theEvent); } + +private: + + Handle(AIS_InteractiveContext) myContext; //!< interactive context + Handle(V3d_View) myView; //!< 3D view + Handle(Prs3d_TextAspect) myTextStyle; //!< text style for OSD elements + Handle(AIS_ViewCube) myViewCube; //!< view cube object + TCollection_AsciiString myCanvasId; //!< canvas element id on HTML page + Aspect_Touch myClickTouch; //!< single touch position for handling clicks + OSD_Timer myDoubleTapTimer; //!< timer for handling double tap + float myDevicePixelRatio; //!< device pixel ratio for handling high DPI displays + unsigned int myUpdateRequests; //!< counter for unhandled update requests + +}; + +#endif // _WasmOcctView_HeaderFile diff --git a/samples/webgl/WasmVKeys.h b/samples/webgl/WasmVKeys.h new file mode 100644 index 0000000000..3c8bb326a0 --- /dev/null +++ b/samples/webgl/WasmVKeys.h @@ -0,0 +1,264 @@ +// Copyright (c) 2019 OPEN CASCADE SAS +// +// This file is part of the examples of the Open CASCADE Technology software library. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE + +#ifndef _WasmVKeys_HeaderFile +#define _WasmVKeys_HeaderFile + +#include + +#include + +//! Convert Emscripten mouse buttons into Aspect_VKeyMouse. +inline Aspect_VKeyMouse WasmVKeys_MouseButtonsFromNative (unsigned short theButtons) +{ + Aspect_VKeyMouse aButtons = Aspect_VKeyMouse_NONE; + if ((theButtons & 0x1) != 0) + { + aButtons |= Aspect_VKeyMouse_LeftButton; + } + if ((theButtons & 0x2) != 0) + { + aButtons |= Aspect_VKeyMouse_RightButton; + } + if ((theButtons & 0x4) != 0) + { + aButtons |= Aspect_VKeyMouse_MiddleButton; + } + return aButtons; +} + +//! Convert DOM virtual key into Aspect_VKey. +inline Aspect_VKey WasmVKeys_VirtualKeyFromNative (Standard_Integer theKey) +{ + if (theKey >= DOM_VK_0 + && theKey <= DOM_VK_9) + { + // numpad keys + return Aspect_VKey((theKey - DOM_VK_0) + Aspect_VKey_0); + } + if (theKey >= DOM_VK_A + && theKey <= DOM_VK_Z) + { + // main latin alphabet keys + return Aspect_VKey((theKey - DOM_VK_A) + Aspect_VKey_A); + } + if (theKey >= DOM_VK_F1 + && theKey <= DOM_VK_F24) + { + // special keys + if (theKey <= DOM_VK_F12) + { + return Aspect_VKey((theKey - DOM_VK_F1) + Aspect_VKey_F1); + } + return Aspect_VKey_UNKNOWN; + } + if (theKey >= DOM_VK_NUMPAD0 + && theKey <= DOM_VK_NUMPAD9) + { + // numpad keys + return Aspect_VKey((theKey - DOM_VK_NUMPAD0) + Aspect_VKey_Numpad0); + } + + switch (theKey) + { + case DOM_VK_CANCEL: + case DOM_VK_HELP: + return Aspect_VKey_UNKNOWN; + case DOM_VK_BACK_SPACE: + return Aspect_VKey_Backspace; + case DOM_VK_TAB: + return Aspect_VKey_Tab; + case DOM_VK_CLEAR: + return Aspect_VKey_UNKNOWN; + case DOM_VK_RETURN: + case DOM_VK_ENTER: + return Aspect_VKey_Enter; + case DOM_VK_SHIFT: + return Aspect_VKey_Shift; + case DOM_VK_CONTROL: + return Aspect_VKey_Control; + case DOM_VK_ALT: + return Aspect_VKey_Alt; + case DOM_VK_PAUSE: + case DOM_VK_CAPS_LOCK: + case DOM_VK_KANA: + //case DOM_VK_HANGUL: + case DOM_VK_EISU: + case DOM_VK_JUNJA: + case DOM_VK_FINAL: + case DOM_VK_HANJA: + //case DOM_VK_KANJI: + return Aspect_VKey_UNKNOWN; + case DOM_VK_ESCAPE: + return Aspect_VKey_Escape; + case DOM_VK_CONVERT: + case DOM_VK_NONCONVERT: + case DOM_VK_ACCEPT: + case DOM_VK_MODECHANGE: + return Aspect_VKey_UNKNOWN; + case DOM_VK_SPACE: + return Aspect_VKey_Space; + case DOM_VK_PAGE_UP: + return Aspect_VKey_PageUp; + case DOM_VK_PAGE_DOWN: + return Aspect_VKey_PageDown; + case DOM_VK_END: + return Aspect_VKey_End; + case DOM_VK_HOME: + return Aspect_VKey_Home; + case DOM_VK_LEFT: + return Aspect_VKey_Left; + case DOM_VK_UP: + return Aspect_VKey_Up; + case DOM_VK_RIGHT: + return Aspect_VKey_Right; + case DOM_VK_DOWN: + return Aspect_VKey_Down; + case DOM_VK_SELECT: + case DOM_VK_PRINT: + case DOM_VK_EXECUTE: + case DOM_VK_PRINTSCREEN: + case DOM_VK_INSERT: + return Aspect_VKey_UNKNOWN; + case DOM_VK_DELETE: + return Aspect_VKey_Delete; + case DOM_VK_COLON: + return Aspect_VKey_Comma; + case DOM_VK_SEMICOLON: + return Aspect_VKey_Semicolon; + case DOM_VK_LESS_THAN: + return Aspect_VKey_UNKNOWN; + case DOM_VK_EQUALS: + return Aspect_VKey_Equal; + case DOM_VK_GREATER_THAN: + return Aspect_VKey_UNKNOWN; + case DOM_VK_QUESTION_MARK: + return Aspect_VKey_Slash; + case DOM_VK_AT: // @ key + return Aspect_VKey_UNKNOWN; + case DOM_VK_WIN: + return Aspect_VKey_Meta; + case DOM_VK_CONTEXT_MENU: + case DOM_VK_SLEEP: + return Aspect_VKey_UNKNOWN; + case DOM_VK_MULTIPLY: + return Aspect_VKey_NumpadMultiply; + case DOM_VK_ADD: + return Aspect_VKey_NumpadAdd; + case DOM_VK_SEPARATOR: + return Aspect_VKey_UNKNOWN; + case DOM_VK_SUBTRACT: + return Aspect_VKey_NumpadSubtract; + case DOM_VK_DECIMAL: + return Aspect_VKey_UNKNOWN; + case DOM_VK_DIVIDE: + return Aspect_VKey_NumpadDivide; + case DOM_VK_NUM_LOCK: + return Aspect_VKey_Numlock; + case DOM_VK_SCROLL_LOCK: + return Aspect_VKey_Scroll; + case DOM_VK_WIN_OEM_FJ_JISHO: + case DOM_VK_WIN_OEM_FJ_MASSHOU: + case DOM_VK_WIN_OEM_FJ_TOUROKU: + case DOM_VK_WIN_OEM_FJ_LOYA: + case DOM_VK_WIN_OEM_FJ_ROYA: + case DOM_VK_CIRCUMFLEX: + return Aspect_VKey_UNKNOWN; + case DOM_VK_EXCLAMATION: + case DOM_VK_DOUBLE_QUOTE: + //case DOM_VK_HASH: + case DOM_VK_DOLLAR: + case DOM_VK_PERCENT: + case DOM_VK_AMPERSAND: + case DOM_VK_UNDERSCORE: + case DOM_VK_OPEN_PAREN: + case DOM_VK_CLOSE_PAREN: + case DOM_VK_ASTERISK: + return Aspect_VKey_UNKNOWN; + case DOM_VK_PLUS: + return Aspect_VKey_Plus; + case DOM_VK_PIPE: + case DOM_VK_HYPHEN_MINUS: + return Aspect_VKey_UNKNOWN; + case DOM_VK_OPEN_CURLY_BRACKET: + return Aspect_VKey_BracketLeft; + case DOM_VK_CLOSE_CURLY_BRACKET: + return Aspect_VKey_BracketRight; + case DOM_VK_TILDE: + return Aspect_VKey_Tilde; + case DOM_VK_VOLUME_MUTE: + return Aspect_VKey_VolumeMute; + case DOM_VK_VOLUME_DOWN: + return Aspect_VKey_VolumeDown; + case DOM_VK_VOLUME_UP: + return Aspect_VKey_VolumeUp; + case DOM_VK_COMMA: + return Aspect_VKey_Comma; + case DOM_VK_PERIOD: + return Aspect_VKey_Period; + case DOM_VK_SLASH: + return Aspect_VKey_Slash; + case DOM_VK_BACK_QUOTE: + return Aspect_VKey_UNKNOWN; + case DOM_VK_OPEN_BRACKET: + return Aspect_VKey_BracketLeft; + case DOM_VK_BACK_SLASH: + return Aspect_VKey_Backslash; + case DOM_VK_CLOSE_BRACKET: + return Aspect_VKey_BracketRight; + case DOM_VK_QUOTE: + return Aspect_VKey_UNKNOWN; + case DOM_VK_META: + return Aspect_VKey_Meta; + case DOM_VK_ALTGR: + return Aspect_VKey_Alt; + case DOM_VK_WIN_ICO_HELP: + case DOM_VK_WIN_ICO_00: + case DOM_VK_WIN_ICO_CLEAR: + case DOM_VK_WIN_OEM_RESET: + case DOM_VK_WIN_OEM_JUMP: + case DOM_VK_WIN_OEM_PA1: + case DOM_VK_WIN_OEM_PA2: + case DOM_VK_WIN_OEM_PA3: + case DOM_VK_WIN_OEM_WSCTRL: + case DOM_VK_WIN_OEM_CUSEL: + case DOM_VK_WIN_OEM_ATTN: + case DOM_VK_WIN_OEM_FINISH: + case DOM_VK_WIN_OEM_COPY: + case DOM_VK_WIN_OEM_AUTO: + case DOM_VK_WIN_OEM_ENLW: + case DOM_VK_WIN_OEM_BACKTAB: + case DOM_VK_ATTN: + case DOM_VK_CRSEL: + case DOM_VK_EXSEL: + case DOM_VK_EREOF: + return Aspect_VKey_UNKNOWN; + case DOM_VK_PLAY: + return Aspect_VKey_MediaPlayPause; + case DOM_VK_ZOOM: + case DOM_VK_PA1: + case DOM_VK_WIN_OEM_CLEAR: + return Aspect_VKey_UNKNOWN; + } + return Aspect_VKey_UNKNOWN; +} + +#endif // _WasmVKeys_HeaderFile diff --git a/samples/webgl/main.cpp b/samples/webgl/main.cpp new file mode 100644 index 0000000000..44206bb0cc --- /dev/null +++ b/samples/webgl/main.cpp @@ -0,0 +1,66 @@ +#include + +#include "WasmOcctView.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +//! Global viewer instance. +static WasmOcctView aViewer; + +//! File data read event. +extern "C" void onFileDataRead (void* theOpaque, void* theBuffer, int theDataLen) +{ + const char* aName = theOpaque != NULL ? (const char* )theOpaque : ""; + { + AIS_ListOfInteractive aShapes; + aViewer.Context()->DisplayedObjects (AIS_KOI_Shape, -1, aShapes); + for (AIS_ListOfInteractive::Iterator aShapeIter (aShapes); aShapeIter.More(); aShapeIter.Next()) + { + aViewer.Context()->Remove (aShapeIter.Value(), false); + } + } + + Standard_ArrayStreamBuffer aStreamBuffer ((const char* )theBuffer, theDataLen); + std::istream aStream (&aStreamBuffer); + TopoDS_Shape aShape; + BRep_Builder aBuilder; + BRepTools::Read (aShape, aStream, aBuilder); + + Handle(AIS_Shape) aShapePrs = new AIS_Shape (aShape); + aShapePrs->SetMaterial (Graphic3d_NOM_SILVER); + aViewer.Context()->Display (aShapePrs, AIS_Shaded, 0, false); + aViewer.View()->FitAll (0.01, false); + aViewer.View()->Redraw(); + Message::DefaultMessenger()->Send (TCollection_AsciiString("Loaded file ") + aName, Message_Info); + Message::DefaultMessenger()->Send (OSD_MemInfo::PrintInfo(), Message_Trace); +} + +//! File read error event. +static void onFileReadFailed (void* theOpaque) +{ + const char* aName = (const char* )theOpaque; + Message::DefaultMessenger()->Send (TCollection_AsciiString("Error: unable to load file ") + aName, Message_Fail); +} + +int main() +{ + Message::DefaultMessenger()->Printers().First()->SetTraceLevel (Message_Trace); + Message::DefaultMessenger()->Send (TCollection_AsciiString("NbLogicalProcessors: ") + OSD_Parallel::NbLogicalProcessors(), Message_Trace); + aViewer.run(); + Message::DefaultMessenger()->Send (OSD_MemInfo::PrintInfo(), Message_Trace); + + // load some file + emscripten_async_wget_data ("samples/Ball.brep", (void* )"samples/Ball.brep", onFileDataRead, onFileReadFailed); + return 0; +} diff --git a/samples/webgl/occt-webgl-sample.html b/samples/webgl/occt-webgl-sample.html new file mode 100644 index 0000000000..80f7e6f4d8 --- /dev/null +++ b/samples/webgl/occt-webgl-sample.html @@ -0,0 +1,133 @@ + + + + + +OCCT WebGL Viewer Sample + + + +

OCCT WebGL Viewer Sample

+
+ + +
+ +
+

Console output:

+

+ + + + diff --git a/src/Aspect/Aspect_DisplayConnection.cxx b/src/Aspect/Aspect_DisplayConnection.cxx index 1d5a53bc55..d6643b32e6 100755 --- a/src/Aspect/Aspect_DisplayConnection.cxx +++ b/src/Aspect/Aspect_DisplayConnection.cxx @@ -25,7 +25,7 @@ IMPLEMENT_STANDARD_RTTIEXT(Aspect_DisplayConnection,Standard_Transient) // ======================================================================= Aspect_DisplayConnection::Aspect_DisplayConnection() { -#if !defined(_WIN32) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX)) && !defined(__ANDROID__) && !defined(__QNX__) +#if !defined(_WIN32) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX)) && !defined(__ANDROID__) && !defined(__QNX__) && !defined(__EMSCRIPTEN__) myDisplay = NULL; myIsOwnDisplay = false; OSD_Environment anEnv ("DISPLAY"); @@ -40,7 +40,7 @@ Aspect_DisplayConnection::Aspect_DisplayConnection() // ======================================================================= Aspect_DisplayConnection::~Aspect_DisplayConnection() { -#if !defined(_WIN32) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX)) && !defined(__ANDROID__) && !defined(__QNX__) +#if !defined(_WIN32) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX)) && !defined(__ANDROID__) && !defined(__QNX__) && !defined(__EMSCRIPTEN__) if (myDisplay != NULL && myIsOwnDisplay) { @@ -49,7 +49,7 @@ Aspect_DisplayConnection::~Aspect_DisplayConnection() #endif } -#if !defined(_WIN32) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX)) && !defined(__ANDROID__) && !defined(__QNX__) +#if !defined(_WIN32) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX)) && !defined(__ANDROID__) && !defined(__QNX__) && !defined(__EMSCRIPTEN__) // ======================================================================= // function : Aspect_DisplayConnection // purpose : diff --git a/src/Aspect/Aspect_DisplayConnection.hxx b/src/Aspect/Aspect_DisplayConnection.hxx index 137749536b..e31f0f2cf4 100755 --- a/src/Aspect/Aspect_DisplayConnection.hxx +++ b/src/Aspect/Aspect_DisplayConnection.hxx @@ -20,7 +20,7 @@ #include #include -#if !defined(_WIN32) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX)) && !defined(__ANDROID__) && !defined(__QNX__) +#if !defined(_WIN32) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX)) && !defined(__ANDROID__) && !defined(__QNX__) && !defined(__EMSCRIPTEN__) #include #endif @@ -39,7 +39,7 @@ public: //! Destructor. Close opened connection. Standard_EXPORT ~Aspect_DisplayConnection(); -#if !defined(_WIN32) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX)) && !defined(__ANDROID__) && !defined(__QNX__) +#if !defined(_WIN32) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX)) && !defined(__ANDROID__) && !defined(__QNX__) && !defined(__EMSCRIPTEN__) //! Constructor. Creates connection with display specified in theDisplayName. //! Display name should be in format "hostname:number" or "hostname:number.screen_number", where: //! hostname - Specifies the name of the host machine on which the display is physically attached. diff --git a/src/Aspect/Aspect_FBConfig.hxx b/src/Aspect/Aspect_FBConfig.hxx index ffdb65bc48..bbed1e4e74 100644 --- a/src/Aspect/Aspect_FBConfig.hxx +++ b/src/Aspect/Aspect_FBConfig.hxx @@ -14,7 +14,7 @@ #ifndef _Aspect_FBConfig_HeaderFile #define _Aspect_FBConfig_HeaderFile -#if !defined(_WIN32) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX)) && !defined(__ANDROID__) && !defined(__QNX__) +#if !defined(_WIN32) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX)) && !defined(__ANDROID__) && !defined(__QNX__) && !defined(__EMSCRIPTEN__) typedef struct __GLXFBConfigRec* GLXFBConfig; typedef GLXFBConfig Aspect_FBConfig; // GLXFBConfig* under UNIX #else diff --git a/src/Aspect/Aspect_XWD.hxx b/src/Aspect/Aspect_XWD.hxx index 46fed65bb9..7d248402d2 100644 --- a/src/Aspect/Aspect_XWD.hxx +++ b/src/Aspect/Aspect_XWD.hxx @@ -14,7 +14,7 @@ #ifndef __Aspect_WNTXWD_HXX # define __Aspect_WNTXWD_HXX -#if !defined(_WIN32) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX)) && !defined(__ANDROID__) && !defined(__QNX__) +#if !defined(_WIN32) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX)) && !defined(__ANDROID__) && !defined(__QNX__) && !defined(__EMSCRIPTEN__) # include # else diff --git a/src/Font/Font_FontMgr.cxx b/src/Font/Font_FontMgr.cxx index 530237ac71..0032a4332e 100644 --- a/src/Font/Font_FontMgr.cxx +++ b/src/Font/Font_FontMgr.cxx @@ -81,7 +81,7 @@ IMPLEMENT_STANDARD_RTTIEXT(Font_FontMgr,Standard_Transient) NULL }; - #if !defined(__ANDROID__) && !defined(__APPLE__) + #if !defined(__ANDROID__) && !defined(__APPLE__) && !defined(__EMSCRIPTEN__) // X11 configuration file in plain text format (obsolete - doesn't exists in modern distributives) static Standard_CString myFontServiceConf[] = {"/etc/X11/fs/config", "/usr/X11R6/lib/X11/fs/config", @@ -483,7 +483,7 @@ void Font_FontMgr::InitFontDataBase() #else NCollection_Map aMapOfFontsDirs; -#if !defined(__ANDROID__) && !defined(__APPLE__) +#if !defined(__ANDROID__) && !defined(__APPLE__) && !defined(__EMSCRIPTEN__) if (FcConfig* aFcCfg = FcInitLoadConfig()) { if (FcStrList* aFcFontDir = FcConfigGetFontDirs (aFcCfg)) @@ -586,7 +586,7 @@ void Font_FontMgr::InitFontDataBase() for (NCollection_Map::Iterator anIter (aMapOfFontsDirs); anIter.More(); anIter.Next()) { - #if !defined(__ANDROID__) && !defined(__APPLE__) + #if !defined(__ANDROID__) && !defined(__APPLE__) && !defined(__EMSCRIPTEN__) OSD_File aReadFile (anIter.Value() + "/fonts.dir"); if (!aReadFile.Exists()) { @@ -607,7 +607,7 @@ void Font_FontMgr::InitFontDataBase() } } - #if !defined(__ANDROID__) && !defined(__APPLE__) + #if !defined(__ANDROID__) && !defined(__APPLE__) && !defined(__EMSCRIPTEN__) continue; } diff --git a/src/InterfaceGraphic/InterfaceGraphic.hxx b/src/InterfaceGraphic/InterfaceGraphic.hxx index 43ab88a23a..c533f68cd5 100644 --- a/src/InterfaceGraphic/InterfaceGraphic.hxx +++ b/src/InterfaceGraphic/InterfaceGraphic.hxx @@ -23,7 +23,7 @@ #undef DrawText #endif -#elif !defined(__ANDROID__) && !defined(__QNX__) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX)) +#elif !defined(__ANDROID__) && !defined(__QNX__) && !defined(__EMSCRIPTEN__) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX)) #include diff --git a/src/OSD/OSD_Chronometer.cxx b/src/OSD/OSD_Chronometer.cxx index b2de7b0252..24b1cad3ed 100644 --- a/src/OSD/OSD_Chronometer.cxx +++ b/src/OSD/OSD_Chronometer.cxx @@ -51,7 +51,7 @@ void OSD_Chronometer::GetProcessCPU (Standard_Real& theUserSeconds, Standard_Real& theSystemSeconds) { -#if defined(__linux__) || defined(__FreeBSD__) || defined(__ANDROID__) || defined(__QNX__) +#if defined(__linux__) || defined(__FreeBSD__) || defined(__ANDROID__) || defined(__QNX__) || defined(__EMSCRIPTEN__) static const long aCLK_TCK = sysconf(_SC_CLK_TCK); #else static const long aCLK_TCK = CLK_TCK; diff --git a/src/OSD/OSD_MemInfo.cxx b/src/OSD/OSD_MemInfo.cxx index 277804e203..8a1f6f8a84 100644 --- a/src/OSD/OSD_MemInfo.cxx +++ b/src/OSD/OSD_MemInfo.cxx @@ -116,7 +116,15 @@ void OSD_MemInfo::Update() myCounters[MemHeapUsage] += hinfo._size; } -#elif (defined(__linux__) || defined(__linux)) +#elif (defined(__linux__) || defined(__linux) || defined(__EMSCRIPTEN__)) + const struct mallinfo aMI = mallinfo(); + myCounters[MemHeapUsage] = aMI.uordblks; +#if defined(__EMSCRIPTEN__) + // /proc/%d/status is not emulated - get more info from mallinfo() + myCounters[MemWorkingSet] = aMI.uordblks; + myCounters[MemWorkingSetPeak] = aMI.usmblks; +#endif + // use procfs on Linux char aBuff[4096]; snprintf (aBuff, sizeof(aBuff), "/proc/%d/status", getpid()); @@ -162,10 +170,6 @@ void OSD_MemInfo::Update() } } aFile.close(); - - struct mallinfo aMI = mallinfo(); - myCounters[MemHeapUsage] = aMI.uordblks; - #elif (defined(__APPLE__)) struct task_basic_info aTaskInfo; mach_msg_type_number_t aTaskInfoCount = TASK_BASIC_INFO_COUNT; diff --git a/src/OSD/OSD_Path.cxx b/src/OSD/OSD_Path.cxx index cca6172504..6e5c5836eb 100644 --- a/src/OSD/OSD_Path.cxx +++ b/src/OSD/OSD_Path.cxx @@ -39,6 +39,8 @@ static OSD_SysType whereAmI() return OSD_VMS; #elif defined(__linux__) || defined(__linux) return OSD_LinuxREDHAT; +#elif defined(__EMSCRIPTEN__) + return OSD_LinuxREDHAT; #elif defined(_AIX) || defined(AIX) return OSD_Aix; #else diff --git a/src/OSD/OSD_signal.cxx b/src/OSD/OSD_signal.cxx index 654523afd9..af86c675bb 100644 --- a/src/OSD/OSD_signal.cxx +++ b/src/OSD/OSD_signal.cxx @@ -703,7 +703,7 @@ typedef void (* SIG_PFV) (int); #include -#if !defined(__ANDROID__) && !defined(__QNX__) +#if !defined(__ANDROID__) && !defined(__QNX__) && !defined(__EMSCRIPTEN__) #include #endif diff --git a/src/OpenGl/OpenGl_Context.cxx b/src/OpenGl/OpenGl_Context.cxx index 89a5ca6ab0..40ec4b535a 100644 --- a/src/OpenGl/OpenGl_Context.cxx +++ b/src/OpenGl/OpenGl_Context.cxx @@ -63,6 +63,29 @@ IMPLEMENT_STANDARD_RTTIEXT(OpenGl_Context,Standard_Transient) #include // glXGetProcAddress() #endif +#ifdef __EMSCRIPTEN__ + #include + + //! Check if WebGL extension is available and activate it + //! (usage of extension without activation will generate errors). + static bool checkEnableWebGlExtension (const OpenGl_Context& theCtx, + const char* theExtName) + { + if (!theCtx.CheckExtension (theExtName)) + { + return false; + } + if (EMSCRIPTEN_WEBGL_CONTEXT_HANDLE aWebGlCtx = emscripten_webgl_get_current_context()) + { + if (emscripten_webgl_enable_extension (aWebGlCtx, theExtName)) + { + return true; + } + } + return false; + } +#endif + namespace { static const Handle(OpenGl_Resource) NULL_GL_RESOURCE; @@ -1399,6 +1422,13 @@ void OpenGl_Context::init (const Standard_Boolean theIsCoreProfile) extAnis = CheckExtension ("GL_EXT_texture_filter_anisotropic"); extPDS = IsGlGreaterEqual (3, 0) || CheckExtension ("GL_OES_packed_depth_stencil"); +#ifdef __EMSCRIPTEN__ + if (!extPDS + && checkEnableWebGlExtension (*this, "GL_WEBGL_depth_texture")) + { + extPDS = true; // WebGL 1.0 extension (in WebGL 2.0 core) + } +#endif core11fwd = (OpenGl_GlCore11Fwd* )(&(*myFuncs)); if (IsGlGreaterEqual (2, 0)) @@ -3129,6 +3159,20 @@ void OpenGl_Context::DiagnosticInformation (TColStd_IndexedDataMapOfStringString ReadGlVersion (aDriverVer[0], aDriverVer[1]); addInfo (theDict, "GLvendor", (const char*)::glGetString (GL_VENDOR)); addInfo (theDict, "GLdevice", (const char*)::glGetString (GL_RENDERER)); + #ifdef __EMSCRIPTEN__ + if (checkEnableWebGlExtension (*this, "GL_WEBGL_debug_renderer_info")) + { + if (const char* aVendor = (const char*)::glGetString (0x9245)) + { + addInfo (theDict, "GLunmaskedVendor", aVendor); + } + if (const char* aDevice = (const char*)::glGetString (0x9246)) + { + addInfo (theDict, "GLunmaskedDevice", aDevice); + } + } + #endif + addInfo (theDict, "GLversion", (const char*)::glGetString (GL_VERSION)); if (myGlVerMajor != aDriverVer[0] || myGlVerMinor != aDriverVer[1]) diff --git a/src/OpenGl/OpenGl_Context.hxx b/src/OpenGl/OpenGl_Context.hxx index e9471d8e8e..747c8721a8 100644 --- a/src/OpenGl/OpenGl_Context.hxx +++ b/src/OpenGl/OpenGl_Context.hxx @@ -572,7 +572,7 @@ public: //! basing on ToRenderSRGB() flag. OpenGl_Vec4 Vec4FromQuantityColor (const OpenGl_Vec4& theColor) const { - return ToRenderSRGB() + return myIsSRgbActive ? Vec4LinearFromQuantityColor(theColor) : Vec4sRGBFromQuantityColor (theColor); } diff --git a/src/OpenGl/OpenGl_FrameBuffer.cxx b/src/OpenGl/OpenGl_FrameBuffer.cxx index f65a9abc5f..e9344f9f0d 100644 --- a/src/OpenGl/OpenGl_FrameBuffer.cxx +++ b/src/OpenGl/OpenGl_FrameBuffer.cxx @@ -38,6 +38,26 @@ namespace } return true; } + + //! Return TRUE if GL_DEPTH_STENCIL_ATTACHMENT can be used. + static bool hasDepthStencilAttach (const Handle(OpenGl_Context)& theCtx) + { + #ifdef __EMSCRIPTEN__ + // supported since WebGL 2.0, + // while WebGL 1.0 + GL_WEBGL_depth_texture needs GL_DEPTH_STENCIL_ATTACHMENT + // and NOT separate GL_DEPTH_ATTACHMENT+GL_STENCIL_ATTACHMENT calls which is different to OpenGL ES 2.0 + extension + return theCtx->IsGlGreaterEqual (3, 0) || theCtx->extPDS; + #elif defined(GL_ES_VERSION_2_0) + // supported since OpenGL ES 3.0, + // while OpenGL ES 2.0 + GL_EXT_packed_depth_stencil needs separate GL_DEPTH_ATTACHMENT+GL_STENCIL_ATTACHMENT calls + return theCtx->IsGlGreaterEqual (3, 0); + #else + // available on desktop since OpenGL 3.0 + // or OpenGL 2.0 + GL_ARB_framebuffer_object (GL_EXT_framebuffer_object is unsupported by OCCT) + (void )theCtx; + return true; + #endif + } } // ======================================================================= @@ -188,15 +208,18 @@ Standard_Boolean OpenGl_FrameBuffer::Init (const Handle(OpenGl_Context)& theGlCo } if (myDepthStencilTexture->IsValid()) { - #ifdef GL_DEPTH_STENCIL_ATTACHMENT - theGlContext->arbFBO->glFramebufferTexture2D (GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, - myDepthStencilTexture->GetTarget(), myDepthStencilTexture->TextureId(), 0); - #else - theGlContext->arbFBO->glFramebufferTexture2D (GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, - myDepthStencilTexture->GetTarget(), myDepthStencilTexture->TextureId(), 0); - theGlContext->arbFBO->glFramebufferTexture2D (GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, - myDepthStencilTexture->GetTarget(), myDepthStencilTexture->TextureId(), 0); - #endif + if (hasDepthStencilAttach (theGlContext)) + { + theGlContext->arbFBO->glFramebufferTexture2D (GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, + myDepthStencilTexture->GetTarget(), myDepthStencilTexture->TextureId(), 0); + } + else + { + theGlContext->arbFBO->glFramebufferTexture2D (GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, + myDepthStencilTexture->GetTarget(), myDepthStencilTexture->TextureId(), 0); + theGlContext->arbFBO->glFramebufferTexture2D (GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, + myDepthStencilTexture->GetTarget(), myDepthStencilTexture->TextureId(), 0); + } } if (theGlContext->arbFBO->glCheckFramebufferStatus (GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { @@ -332,32 +355,39 @@ Standard_Boolean OpenGl_FrameBuffer::Init (const Handle(OpenGl_Context)& theGlCo aColorTexture->GetTarget(), aColorTexture->TextureId(), 0); } } + if (myDepthStencilTexture->IsValid()) { - #ifdef GL_DEPTH_STENCIL_ATTACHMENT - theGlContext->arbFBO->glFramebufferTexture2D (GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, - myDepthStencilTexture->GetTarget(), myDepthStencilTexture->TextureId(), 0); - #else - theGlContext->arbFBO->glFramebufferTexture2D (GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, - myDepthStencilTexture->GetTarget(), myDepthStencilTexture->TextureId(), 0); - theGlContext->arbFBO->glFramebufferTexture2D (GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, - myDepthStencilTexture->GetTarget(), myDepthStencilTexture->TextureId(), 0); - #endif + if (hasDepthStencilAttach (theGlContext)) + { + theGlContext->arbFBO->glFramebufferTexture2D (GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, + myDepthStencilTexture->GetTarget(), myDepthStencilTexture->TextureId(), 0); + } + else + { + theGlContext->arbFBO->glFramebufferTexture2D (GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, + myDepthStencilTexture->GetTarget(), myDepthStencilTexture->TextureId(), 0); + theGlContext->arbFBO->glFramebufferTexture2D (GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, + myDepthStencilTexture->GetTarget(), myDepthStencilTexture->TextureId(), 0); + } } else if (myGlDepthRBufferId != NO_RENDERBUFFER) { - #ifdef GL_DEPTH_STENCIL_ATTACHMENT - theGlContext->arbFBO->glFramebufferRenderbuffer (GL_FRAMEBUFFER, hasStencilRB ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT, - GL_RENDERBUFFER, myGlDepthRBufferId); - #else - theGlContext->arbFBO->glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, - GL_RENDERBUFFER, myGlDepthRBufferId); - if (hasStencilRB) + if (hasDepthStencilAttach (theGlContext) && hasStencilRB) { - theGlContext->arbFBO->glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, + theGlContext->arbFBO->glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, myGlDepthRBufferId); } - #endif + else + { + theGlContext->arbFBO->glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, + GL_RENDERBUFFER, myGlDepthRBufferId); + if (hasStencilRB) + { + theGlContext->arbFBO->glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, + GL_RENDERBUFFER, myGlDepthRBufferId); + } + } } if (theGlContext->arbFBO->glCheckFramebufferStatus (GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { @@ -486,18 +516,21 @@ Standard_Boolean OpenGl_FrameBuffer::InitWithRB (const Handle(OpenGl_Context)& t GL_RENDERBUFFER, myGlColorRBufferId); if (myGlDepthRBufferId != NO_RENDERBUFFER) { - #ifdef GL_DEPTH_STENCIL_ATTACHMENT - theGlCtx->arbFBO->glFramebufferRenderbuffer (GL_FRAMEBUFFER, hasStencilRB ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT, - GL_RENDERBUFFER, myGlDepthRBufferId); - #else - theGlCtx->arbFBO->glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, - GL_RENDERBUFFER, myGlDepthRBufferId); - if (hasStencilRB) + if (hasDepthStencilAttach (theGlCtx) && hasStencilRB) { - theGlCtx->arbFBO->glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, + theGlCtx->arbFBO->glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, myGlDepthRBufferId); } - #endif + else + { + theGlCtx->arbFBO->glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, + GL_RENDERBUFFER, myGlDepthRBufferId); + if (hasStencilRB) + { + theGlCtx->arbFBO->glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, + GL_RENDERBUFFER, myGlDepthRBufferId); + } + } } if (theGlCtx->arbFBO->glCheckFramebufferStatus (GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { diff --git a/src/OpenGl/OpenGl_GlFunctions.hxx b/src/OpenGl/OpenGl_GlFunctions.hxx index 20e6eaab1a..ec2f93d096 100644 --- a/src/OpenGl/OpenGl_GlFunctions.hxx +++ b/src/OpenGl/OpenGl_GlFunctions.hxx @@ -54,7 +54,7 @@ #include #endif #define __X_GL_H // prevent chaotic gl.h inclusions to avoid compile errors -#elif defined(HAVE_GLES2) || defined(OCCT_UWP) || defined(__ANDROID__) || defined(__QNX__) +#elif defined(HAVE_GLES2) || defined(OCCT_UWP) || defined(__ANDROID__) || defined(__QNX__) || defined(__EMSCRIPTEN__) #if defined(_WIN32) // Angle OpenGL ES headers do not define function prototypes even for core functions, // however OCCT is expected to be linked against libGLESv2 @@ -160,6 +160,7 @@ #define GL_DEPTH_STENCIL 0x84F9 #define GL_UNSIGNED_INT_24_8 0x84FA #define GL_DEPTH24_STENCIL8 0x88F0 + #define GL_DEPTH_STENCIL_ATTACHMENT 0x821A // OpenGL ES 3.0+ #define GL_DEPTH_COMPONENT24 0x81A6 @@ -225,7 +226,7 @@ #define GL_PATCHES 0x000E #endif -#if !defined(HAVE_EGL) && (defined(__ANDROID__) || defined(__QNX__) || defined(HAVE_GLES2) || defined(OCCT_UWP)) +#if !defined(HAVE_EGL) && (defined(__ANDROID__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(HAVE_GLES2) || defined(OCCT_UWP)) #define HAVE_EGL #endif diff --git a/src/OpenGl/OpenGl_GraphicDriver.cxx b/src/OpenGl/OpenGl_GraphicDriver.cxx index 1dddb00d59..1694e17664 100644 --- a/src/OpenGl/OpenGl_GraphicDriver.cxx +++ b/src/OpenGl/OpenGl_GraphicDriver.cxx @@ -44,11 +44,11 @@ IMPLEMENT_STANDARD_RTTIEXT(OpenGl_GraphicDriver,Graphic3d_GraphicDriver) #include #endif -#if !defined(_WIN32) && !defined(__ANDROID__) && !defined(__QNX__) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX)) +#if !defined(_WIN32) && !defined(__ANDROID__) && !defined(__QNX__) && !defined(__EMSCRIPTEN__) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX)) #include // XOpenDisplay() #endif -#if defined(HAVE_EGL) || defined(HAVE_GLES2) || defined(OCCT_UWP) || defined(__ANDROID__) || defined(__QNX__) +#if defined(HAVE_EGL) || defined(HAVE_GLES2) || defined(OCCT_UWP) || defined(__ANDROID__) || defined(__QNX__) || defined(__EMSCRIPTEN__) #include #ifndef EGL_OPENGL_ES3_BIT #define EGL_OPENGL_ES3_BIT 0x00000040 @@ -59,7 +59,7 @@ namespace { static const Handle(OpenGl_Context) TheNullGlCtx; -#if defined(HAVE_EGL) || defined(HAVE_GLES2) || defined(OCCT_UWP) || defined(__ANDROID__) || defined(__QNX__) +#if defined(HAVE_EGL) || defined(HAVE_GLES2) || defined(OCCT_UWP) || defined(__ANDROID__) || defined(__QNX__) || defined(__EMSCRIPTEN__) //! Wrapper over eglChooseConfig() called with preferred defaults. static EGLConfig chooseEglSurfConfig (EGLDisplay theDisplay) { @@ -120,7 +120,7 @@ OpenGl_GraphicDriver::OpenGl_GraphicDriver (const Handle(Aspect_DisplayConnectio const Standard_Boolean theToInitialize) : Graphic3d_GraphicDriver (theDisp), myIsOwnContext (Standard_False), -#if defined(HAVE_EGL) || defined(HAVE_GLES2) || defined(OCCT_UWP) || defined(__ANDROID__) || defined(__QNX__) +#if defined(HAVE_EGL) || defined(HAVE_GLES2) || defined(OCCT_UWP) || defined(__ANDROID__) || defined(__QNX__) || defined(__EMSCRIPTEN__) myEglDisplay ((Aspect_Display )EGL_NO_DISPLAY), myEglContext ((Aspect_RenderingContext )EGL_NO_CONTEXT), myEglConfig (NULL), @@ -129,7 +129,7 @@ OpenGl_GraphicDriver::OpenGl_GraphicDriver (const Handle(Aspect_DisplayConnectio myMapOfView (1, NCollection_BaseAllocator::CommonBaseAllocator()), myMapOfStructure (1, NCollection_BaseAllocator::CommonBaseAllocator()) { -#if !defined(_WIN32) && !defined(__ANDROID__) && !defined(__QNX__) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX)) +#if !defined(_WIN32) && !defined(__ANDROID__) && !defined(__QNX__) && !defined(__EMSCRIPTEN__) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX)) if (myDisplayConnection.IsNull()) { //throw Aspect_GraphicDeviceDefinitionError("OpenGl_GraphicDriver: cannot connect to X server!"); @@ -228,7 +228,7 @@ void OpenGl_GraphicDriver::ReleaseContext() aWindow->GetGlContext()->forcedRelease(); } -#if defined(HAVE_EGL) || defined(HAVE_GLES2) || defined(OCCT_UWP) || defined(__ANDROID__) || defined(__QNX__) +#if defined(HAVE_EGL) || defined(HAVE_GLES2) || defined(OCCT_UWP) || defined(__ANDROID__) || defined(__QNX__) || defined(__EMSCRIPTEN__) if (myIsOwnContext) { if (myEglContext != (Aspect_RenderingContext )EGL_NO_CONTEXT) @@ -263,9 +263,9 @@ void OpenGl_GraphicDriver::ReleaseContext() Standard_Boolean OpenGl_GraphicDriver::InitContext() { ReleaseContext(); -#if defined(HAVE_EGL) || defined(HAVE_GLES2) || defined(OCCT_UWP) || defined(__ANDROID__) || defined(__QNX__) +#if defined(HAVE_EGL) || defined(HAVE_GLES2) || defined(OCCT_UWP) || defined(__ANDROID__) || defined(__QNX__) || defined(__EMSCRIPTEN__) -#if !defined(_WIN32) && !defined(__ANDROID__) && !defined(__QNX__) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX)) +#if !defined(_WIN32) && !defined(__ANDROID__) && !defined(__QNX__) && !defined(__EMSCRIPTEN__) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX)) if (myDisplayConnection.IsNull()) { return Standard_False; @@ -337,7 +337,7 @@ Standard_Boolean OpenGl_GraphicDriver::InitContext() return Standard_True; } -#if defined(HAVE_EGL) || defined(HAVE_GLES2) || defined(OCCT_UWP) || defined(__ANDROID__) || defined(__QNX__) +#if defined(HAVE_EGL) || defined(HAVE_GLES2) || defined(OCCT_UWP) || defined(__ANDROID__) || defined(__QNX__) || defined(__EMSCRIPTEN__) // ======================================================================= // function : InitEglContext // purpose : @@ -347,7 +347,7 @@ Standard_Boolean OpenGl_GraphicDriver::InitEglContext (Aspect_Display t void* theEglConfig) { ReleaseContext(); -#if !defined(_WIN32) && !defined(__ANDROID__) && !defined(__QNX__) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX)) +#if !defined(_WIN32) && !defined(__ANDROID__) && !defined(__QNX__) && !defined(__EMSCRIPTEN__) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX)) if (myDisplayConnection.IsNull()) { return Standard_False; @@ -733,7 +733,7 @@ Standard_Boolean OpenGl_GraphicDriver::ViewExists (const Handle(Aspect_Window)& #else NSView* TheSpecifiedWindowId = THEWindow->HView(); #endif -#elif defined(__ANDROID__) || defined(__QNX__) || defined(OCCT_UWP) +#elif defined(__ANDROID__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(OCCT_UWP) (void )AWindow; int TheSpecifiedWindowId = -1; #else @@ -759,7 +759,7 @@ Standard_Boolean OpenGl_GraphicDriver::ViewExists (const Handle(Aspect_Window)& #else NSView* TheWindowIdOfView = theWindow->HView(); #endif -#elif defined(__ANDROID__) || defined(__QNX__) || defined(OCCT_UWP) +#elif defined(__ANDROID__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(OCCT_UWP) int TheWindowIdOfView = 0; #else const Handle(Xw_Window) theWindow = Handle(Xw_Window)::DownCast (AspectWindow); diff --git a/src/OpenGl/OpenGl_GraphicDriver.hxx b/src/OpenGl/OpenGl_GraphicDriver.hxx index 4c63bb02e8..09e8438e16 100644 --- a/src/OpenGl/OpenGl_GraphicDriver.hxx +++ b/src/OpenGl/OpenGl_GraphicDriver.hxx @@ -68,7 +68,7 @@ public: //! Perform initialization of default OpenGL context. Standard_EXPORT Standard_Boolean InitContext(); -#if defined(HAVE_EGL) || defined(HAVE_GLES2) || defined(OCCT_UWP) || defined(__ANDROID__) || defined(__QNX__) +#if defined(HAVE_EGL) || defined(HAVE_GLES2) || defined(OCCT_UWP) || defined(__ANDROID__) || defined(__QNX__) || defined(__EMSCRIPTEN__) //! Initialize default OpenGL context using existing one. //! @param theEglDisplay EGL connection to the Display //! @param theEglContext EGL rendering context @@ -168,7 +168,7 @@ public: //! any context will be returned otherwise Standard_EXPORT const Handle(OpenGl_Context)& GetSharedContext (bool theBound = false) const; -#if defined(HAVE_EGL) || defined(HAVE_GLES2) || defined(OCCT_UWP) || defined(__ANDROID__) || defined(__QNX__) +#if defined(HAVE_EGL) || defined(HAVE_GLES2) || defined(OCCT_UWP) || defined(__ANDROID__) || defined(__QNX__) || defined(__EMSCRIPTEN__) Aspect_Display getRawGlDisplay() const { return myEglDisplay; } Aspect_RenderingContext getRawGlContext() const { return myEglContext; } void* getRawGlConfig() const { return myEglConfig; } @@ -188,7 +188,7 @@ public: protected: Standard_Boolean myIsOwnContext; //!< indicates that shared context has been created within OpenGl_GraphicDriver -#if defined(HAVE_EGL) || defined(HAVE_GLES2) || defined(OCCT_UWP) || defined(__ANDROID__) || defined(__QNX__) +#if defined(HAVE_EGL) || defined(HAVE_GLES2) || defined(OCCT_UWP) || defined(__ANDROID__) || defined(__QNX__) || defined(__EMSCRIPTEN__) Aspect_Display myEglDisplay; //!< EGL connection to the Display : EGLDisplay Aspect_RenderingContext myEglContext; //!< EGL rendering context : EGLContext void* myEglConfig; //!< EGL configuration : EGLConfig diff --git a/src/OpenGl/OpenGl_ShaderManager.cxx b/src/OpenGl/OpenGl_ShaderManager.cxx index b7f3c4069f..c7328cedd3 100644 --- a/src/OpenGl/OpenGl_ShaderManager.cxx +++ b/src/OpenGl/OpenGl_ShaderManager.cxx @@ -1630,6 +1630,15 @@ int OpenGl_ShaderManager::defaultGlslVersion (const Handle(Graphic3d_ShaderProgr } (void )toUseDerivates; #else + +#if defined(__EMSCRIPTEN__) + if (myContext->IsGlGreaterEqual (3, 0)) + { + // consider this is browser responsibility to provide working WebGL 2.0 implementation + // and black-list broken drivers (there is no OpenGL ES greater than 3.0) + theProgram->SetHeader ("#version 300 es"); + } +#endif // prefer "100 es" on OpenGL ES 3.0- devices (save the features unavailable before "300 es") // and "300 es" on OpenGL ES 3.1+ devices if (myContext->IsGlGreaterEqual (3, 1)) diff --git a/src/OpenGl/OpenGl_Window.cxx b/src/OpenGl/OpenGl_Window.cxx index 918688f74f..b01f7f58c2 100644 --- a/src/OpenGl/OpenGl_Window.cxx +++ b/src/OpenGl/OpenGl_Window.cxx @@ -224,6 +224,7 @@ OpenGl_Window::OpenGl_Window (const Handle(OpenGl_GraphicDriver)& theDriver, //throw Aspect_GraphicDeviceDefinitionError("OpenGl_Window, EGL is unable to retrieve current surface!"); if (anEglConfig != NULL) { + #if !defined(__EMSCRIPTEN__) // eglCreatePbufferSurface() is not implemented by Emscripten EGL const int aSurfAttribs[] = { EGL_WIDTH, myWidth, @@ -237,6 +238,7 @@ OpenGl_Window::OpenGl_Window (const Handle(OpenGl_GraphicDriver)& theDriver, { throw Aspect_GraphicDeviceDefinitionError("OpenGl_Window, EGL is unable to create off-screen surface!"); } + #endif } myGlContext->PushMessage (GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_PORTABILITY, 0, GL_DEBUG_SEVERITY_LOW, "OpenGl_Window::CreateWindow: WARNING, a Window is created without a EGL Surface!"); diff --git a/src/Quantity/Quantity_ColorRGBA.cxx b/src/Quantity/Quantity_ColorRGBA.cxx index 4a7c664dd1..4ce134c4b6 100644 --- a/src/Quantity/Quantity_ColorRGBA.cxx +++ b/src/Quantity/Quantity_ColorRGBA.cxx @@ -15,7 +15,7 @@ #include -#include +#include #include #include @@ -69,7 +69,7 @@ namespace Standard_ASSERT_RETURN (theColorComponentBase >= 2, __FUNCTION__ ": 'theColorComponentBase' must be greater than 1.", 0.0f); - Graphic3d_Vec4 aColor (1.0f); + NCollection_Vec4 aColor (1.0f); if (hasAlphaComponent) { const Standard_ShortReal anAlphaComponent = takeColorComponentFromInteger (theColorInteger, diff --git a/src/Shaders/Declarations.glsl b/src/Shaders/Declarations.glsl index 05eafa88b0..d0c4f1ec97 100644 --- a/src/Shaders/Declarations.glsl +++ b/src/Shaders/Declarations.glsl @@ -102,7 +102,7 @@ uniform mat4 occWorldViewMatrixInverseTranspose; //!< Transpose of the inverse uniform mat4 occProjectionMatrixInverseTranspose; //!< Transpose of the inverse of the projection matrix uniform mat4 occModelWorldMatrixInverseTranspose; //!< Transpose of the inverse of the model-world matrix -// light type enumeration +// light type enumeration (same as Graphic3d_TypeOfLightSource) const int OccLightType_Direct = 1; //!< directional light source const int OccLightType_Point = 2; //!< isotropic point light source const int OccLightType_Spot = 3; //!< spot light source @@ -111,16 +111,36 @@ const int OccLightType_Spot = 3; //!< spot light source uniform vec4 occLightAmbient; //!< Cumulative ambient color #if defined(THE_MAX_LIGHTS) && (THE_MAX_LIGHTS > 0) uniform THE_PREC_ENUM int occLightSourcesCount; //!< Total number of light sources -int occLight_Type (in int theId); //!< Type of light source -int occLight_IsHeadlight (in int theId); //!< Is light a headlight? -vec4 occLight_Diffuse (in int theId); //!< Diffuse intensity for specified light source -vec4 occLight_Specular (in int theId); //!< Specular intensity (currently - equals to diffuse intencity) -vec4 occLight_Position (in int theId); //!< Position of specified light source -vec4 occLight_SpotDirection (in int theId); //!< Direction of specified spot light source -float occLight_ConstAttenuation (in int theId); //!< Const attenuation factor of positional light source -float occLight_LinearAttenuation (in int theId); //!< Linear attenuation factor of positional light source -float occLight_SpotCutOff (in int theId); //!< Maximum spread angle of the spot light (in radians) -float occLight_SpotExponent (in int theId); //!< Attenuation of the spot light intensity (from 0 to 1) + +//! Type of light source, int (see OccLightType enum). +#define occLight_Type(theId) occLightSourcesTypes[theId].x + +//! Is light a headlight, int? +#define occLight_IsHeadlight(theId) occLightSourcesTypes[theId].y + +//! Specular intensity (equals to diffuse), vec4. +#define occLight_Specular(theId) occLightSources[theId * 4 + 0] + +//! Position of specified light source, vec4. +#define occLight_Position(theId) occLightSources[theId * 4 + 1] + +//! Direction of specified spot light source, vec4. +#define occLight_SpotDirection(theId) occLightSources[theId * 4 + 2] + +//! Maximum spread angle of the spot light (in radians), float. +#define occLight_SpotCutOff(theId) occLightSources[theId * 4 + 3].z + +//! Attenuation of the spot light intensity (from 0 to 1), float. +#define occLight_SpotExponent(theId) occLightSources[theId * 4 + 3].w + +//! Diffuse intensity (equals to Specular), vec4. +#define occLight_Diffuse(theId) occLightSources[theId * 4 + 0] + +//! Const attenuation factor of positional light source, float. +#define occLight_ConstAttenuation(theId) occLightSources[theId * 4 + 3].x + +//! Linear attenuation factor of positional light source, float. +#define occLight_LinearAttenuation(theId) occLightSources[theId * 4 + 3].y #endif // Front material properties accessors diff --git a/src/Shaders/DeclarationsImpl.glsl b/src/Shaders/DeclarationsImpl.glsl index 5fd67ed020..37ac72b1d8 100644 --- a/src/Shaders/DeclarationsImpl.glsl +++ b/src/Shaders/DeclarationsImpl.glsl @@ -21,18 +21,6 @@ void occSetFragColor (in vec4 theColor) // arrays of light sources uniform THE_PREC_ENUM ivec2 occLightSourcesTypes[THE_MAX_LIGHTS]; //!< packed light sources types uniform vec4 occLightSources[THE_MAX_LIGHTS * 4]; //!< packed light sources parameters - -// light source properties accessors -int occLight_Type (in int theId) { return occLightSourcesTypes[theId].x; } -int occLight_IsHeadlight (in int theId) { return occLightSourcesTypes[theId].y; } -vec4 occLight_Diffuse (in int theId) { return occLightSources[theId * 4 + 0]; } -vec4 occLight_Specular (in int theId) { return occLightSources[theId * 4 + 0]; } -vec4 occLight_Position (in int theId) { return occLightSources[theId * 4 + 1]; } -vec4 occLight_SpotDirection (in int theId) { return occLightSources[theId * 4 + 2]; } -float occLight_ConstAttenuation (in int theId) { return occLightSources[theId * 4 + 3].x; } -float occLight_LinearAttenuation (in int theId) { return occLightSources[theId * 4 + 3].y; } -float occLight_SpotCutOff (in int theId) { return occLightSources[theId * 4 + 3].z; } -float occLight_SpotExponent (in int theId) { return occLightSources[theId * 4 + 3].w; } #endif // material state diff --git a/src/Shaders/Shaders_DeclarationsImpl_glsl.pxx b/src/Shaders/Shaders_DeclarationsImpl_glsl.pxx index 6b454b066e..71a6b03674 100644 --- a/src/Shaders/Shaders_DeclarationsImpl_glsl.pxx +++ b/src/Shaders/Shaders_DeclarationsImpl_glsl.pxx @@ -24,18 +24,6 @@ static const char Shaders_DeclarationsImpl_glsl[] = "// arrays of light sources\n" "uniform THE_PREC_ENUM ivec2 occLightSourcesTypes[THE_MAX_LIGHTS]; //!< packed light sources types\n" "uniform vec4 occLightSources[THE_MAX_LIGHTS * 4]; //!< packed light sources parameters\n" - "\n" - "// light source properties accessors\n" - "int occLight_Type (in int theId) { return occLightSourcesTypes[theId].x; }\n" - "int occLight_IsHeadlight (in int theId) { return occLightSourcesTypes[theId].y; }\n" - "vec4 occLight_Diffuse (in int theId) { return occLightSources[theId * 4 + 0]; }\n" - "vec4 occLight_Specular (in int theId) { return occLightSources[theId * 4 + 0]; }\n" - "vec4 occLight_Position (in int theId) { return occLightSources[theId * 4 + 1]; }\n" - "vec4 occLight_SpotDirection (in int theId) { return occLightSources[theId * 4 + 2]; }\n" - "float occLight_ConstAttenuation (in int theId) { return occLightSources[theId * 4 + 3].x; }\n" - "float occLight_LinearAttenuation (in int theId) { return occLightSources[theId * 4 + 3].y; }\n" - "float occLight_SpotCutOff (in int theId) { return occLightSources[theId * 4 + 3].z; }\n" - "float occLight_SpotExponent (in int theId) { return occLightSources[theId * 4 + 3].w; }\n" "#endif\n" "\n" "// material state\n" diff --git a/src/Shaders/Shaders_Declarations_glsl.pxx b/src/Shaders/Shaders_Declarations_glsl.pxx index 39929a58fc..12af3c4dba 100644 --- a/src/Shaders/Shaders_Declarations_glsl.pxx +++ b/src/Shaders/Shaders_Declarations_glsl.pxx @@ -105,7 +105,7 @@ static const char Shaders_Declarations_glsl[] = "uniform mat4 occProjectionMatrixInverseTranspose; //!< Transpose of the inverse of the projection matrix\n" "uniform mat4 occModelWorldMatrixInverseTranspose; //!< Transpose of the inverse of the model-world matrix\n" "\n" - "// light type enumeration\n" + "// light type enumeration (same as Graphic3d_TypeOfLightSource)\n" "const int OccLightType_Direct = 1; //!< directional light source\n" "const int OccLightType_Point = 2; //!< isotropic point light source\n" "const int OccLightType_Spot = 3; //!< spot light source\n" @@ -114,16 +114,36 @@ static const char Shaders_Declarations_glsl[] = "uniform vec4 occLightAmbient; //!< Cumulative ambient color\n" "#if defined(THE_MAX_LIGHTS) && (THE_MAX_LIGHTS > 0)\n" "uniform THE_PREC_ENUM int occLightSourcesCount; //!< Total number of light sources\n" - "int occLight_Type (in int theId); //!< Type of light source\n" - "int occLight_IsHeadlight (in int theId); //!< Is light a headlight?\n" - "vec4 occLight_Diffuse (in int theId); //!< Diffuse intensity for specified light source\n" - "vec4 occLight_Specular (in int theId); //!< Specular intensity (currently - equals to diffuse intencity)\n" - "vec4 occLight_Position (in int theId); //!< Position of specified light source\n" - "vec4 occLight_SpotDirection (in int theId); //!< Direction of specified spot light source\n" - "float occLight_ConstAttenuation (in int theId); //!< Const attenuation factor of positional light source\n" - "float occLight_LinearAttenuation (in int theId); //!< Linear attenuation factor of positional light source\n" - "float occLight_SpotCutOff (in int theId); //!< Maximum spread angle of the spot light (in radians)\n" - "float occLight_SpotExponent (in int theId); //!< Attenuation of the spot light intensity (from 0 to 1)\n" + "\n" + "//! Type of light source, int (see OccLightType enum).\n" + "#define occLight_Type(theId) occLightSourcesTypes[theId].x\n" + "\n" + "//! Is light a headlight, int?\n" + "#define occLight_IsHeadlight(theId) occLightSourcesTypes[theId].y\n" + "\n" + "//! Specular intensity (equals to diffuse), vec4.\n" + "#define occLight_Specular(theId) occLightSources[theId * 4 + 0]\n" + "\n" + "//! Position of specified light source, vec4.\n" + "#define occLight_Position(theId) occLightSources[theId * 4 + 1]\n" + "\n" + "//! Direction of specified spot light source, vec4.\n" + "#define occLight_SpotDirection(theId) occLightSources[theId * 4 + 2]\n" + "\n" + "//! Maximum spread angle of the spot light (in radians), float.\n" + "#define occLight_SpotCutOff(theId) occLightSources[theId * 4 + 3].z\n" + "\n" + "//! Attenuation of the spot light intensity (from 0 to 1), float.\n" + "#define occLight_SpotExponent(theId) occLightSources[theId * 4 + 3].w\n" + "\n" + "//! Diffuse intensity (equals to Specular), vec4.\n" + "#define occLight_Diffuse(theId) occLightSources[theId * 4 + 0]\n" + "\n" + "//! Const attenuation factor of positional light source, float.\n" + "#define occLight_ConstAttenuation(theId) occLightSources[theId * 4 + 3].x\n" + "\n" + "//! Linear attenuation factor of positional light source, float.\n" + "#define occLight_LinearAttenuation(theId) occLightSources[theId * 4 + 3].y\n" "#endif\n" "\n" "// Front material properties accessors\n" diff --git a/src/Standard/Standard_Atomic.hxx b/src/Standard/Standard_Atomic.hxx index 678e211dc2..f220fbadb1 100644 --- a/src/Standard/Standard_Atomic.hxx +++ b/src/Standard/Standard_Atomic.hxx @@ -44,7 +44,7 @@ inline int Standard_Atomic_Decrement (volatile int* theValue); inline bool Standard_Atomic_CompareAndSwap (volatile int* theValue, int theOldValue, int theNewValue); // Platform-dependent implementation -#if defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4) +#if defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4) || defined(__EMSCRIPTEN__) // gcc explicitly defines the macros __GCC_HAVE_SYNC_COMPARE_AND_SWAP_* // starting with version 4.4+, although built-in functions // are available since 4.1.x. However unless __GCC_HAVE_SYNC_COMPARE_AND_SWAP_* diff --git a/src/Xw/Xw_Window.cxx b/src/Xw/Xw_Window.cxx index 37d7ed690e..62a9889ae3 100644 --- a/src/Xw/Xw_Window.cxx +++ b/src/Xw/Xw_Window.cxx @@ -15,7 +15,7 @@ #include -#if !defined(_WIN32) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX)) && !defined(__ANDROID__) && !defined(__QNX__) +#if !defined(_WIN32) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX)) && !defined(__ANDROID__) && !defined(__QNX__) && !defined(__EMSCRIPTEN__) #include #include diff --git a/src/Xw/Xw_Window.hxx b/src/Xw/Xw_Window.hxx index a9d806e972..6b21974f6c 100644 --- a/src/Xw/Xw_Window.hxx +++ b/src/Xw/Xw_Window.hxx @@ -16,7 +16,7 @@ #ifndef _Xw_Window_H__ #define _Xw_Window_H__ -#if !defined(_WIN32) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX)) && !defined(__ANDROID__) && !defined(__QNX__) +#if !defined(_WIN32) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX)) && !defined(__ANDROID__) && !defined(__QNX__) && !defined(__EMSCRIPTEN__) #include