├── .gitignore ├── metal_util.h ├── cmake ├── glm.cmake └── FindDawn.cmake ├── index.html.in ├── LICENSE.md ├── README.md ├── metal_util.mm ├── arcball_camera.h ├── CMakeLists.txt ├── arcball_camera.cpp ├── .clang-format └── main.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.raw 3 | build/ 4 | cmake-build 5 | cmake-ninja 6 | .DS_Store 7 | compile_commands.json 8 | .cache/ 9 | emcmake-build* 10 | cmake-build* 11 | main.js 12 | main.wasm 13 | cmake-build-release 14 | ninja-gen 15 | .clangd 16 | -------------------------------------------------------------------------------- /metal_util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace metal { 8 | 9 | // TODO: Could do the same for D3D12 and Vulkan, quite similar setup to ChameleonRT 10 | struct Context; 11 | 12 | std::shared_ptr make_context(SDL_Window *window); 13 | 14 | wgpu::SurfaceDescriptorFromMetalLayer surface_descriptor(std::shared_ptr &context); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /cmake/glm.cmake: -------------------------------------------------------------------------------- 1 | ExternalProject_Add(glm_ext 2 | PREFIX glm 3 | DOWNLOAD_DIR glm 4 | STAMP_DIR glm/stamp 5 | SOURCE_DIR glm/src 6 | BINARY_DIR glm 7 | URL "https://github.com/g-truc/glm/releases/download/0.9.9.8/glm-0.9.9.8.zip" 8 | URL_HASH "SHA256=37e2a3d62ea3322e43593c34bae29f57e3e251ea89f4067506c94043769ade4c" 9 | CONFIGURE_COMMAND "" 10 | BUILD_COMMAND "" 11 | INSTALL_COMMAND "" 12 | BUILD_ALWAYS OFF 13 | ) 14 | 15 | set(GLM_INCLUDE_DIRS ${CMAKE_CURRENT_BINARY_DIR}/glm/src) 16 | 17 | add_library(glm INTERFACE) 18 | 19 | add_dependencies(glm glm_ext) 20 | 21 | target_include_directories(glm INTERFACE 22 | ${GLM_INCLUDE_DIRS}) 23 | 24 | 25 | -------------------------------------------------------------------------------- /index.html.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WebGPU Starter 5 | 6 | 7 | 8 | 9 | 10 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Will Usher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Archived/Deprecated 2 | See my updated template repo: https://github.com/Twinklebear/webgpu-cpp-wasm which builds 3 | native + wasm SDL2 + WebGPU apps. 4 | 5 | # WebGPU C++ Starter Project 6 | 7 | A starter code for cross-platform (i.e., web & native) C++ WebGPU projects. 8 | 9 | ## Building for Web with Emscripten 10 | 11 | You can build the project using Emscripten's CMake wrapper. 12 | 13 | ``` 14 | mkdir emcmake-build 15 | emcmake cmake .. 16 | cmake --build . 17 | ``` 18 | 19 | You can then run a webserver in the build directory and view the application in 20 | a browser that supports WebGPU. 21 | 22 | ``` 23 | python3 -m http.server 24 | # navigate to localhost:8000 to see the triangle! 25 | ``` 26 | 27 | ## Building for Native with Dawn 28 | 29 | The application uses [Dawn](https://dawn.googlesource.com/dawn/) to provide an 30 | implementation of WebGPU for native platforms. Carl Woffenden has a good 31 | [guide](https://github.com/cwoffenden/hello-webgpu/blob/master/lib/README.md) 32 | about building Dawn which you can follow to build Dawn. 33 | 34 | ``` 35 | mkdir cmake-build 36 | cmake .. -DDawn_DIR= ` 37 | -DCMAKE_TOOLCHAIN_FILE= 38 | cmake --build . 39 | ``` 40 | 41 | If on Windows copy over the dll's in Dawn's build directory, 42 | and you can then run the native app. On Mac or Linux, the dylibs/sos 43 | just need to be in the path of the executable. 44 | 45 | Then on Windows you can run: 46 | 47 | ``` 48 | .//wgpu-starter.exe 49 | ``` 50 | 51 | Or on Mac/Linux: 52 | 53 | ``` 54 | ./wgpu-starter 55 | ``` 56 | -------------------------------------------------------------------------------- /metal_util.mm: -------------------------------------------------------------------------------- 1 | #include "metal_util.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace metal { 10 | 11 | struct Context { 12 | id device = nullptr; 13 | CAMetalLayer *layer = nullptr; 14 | 15 | Context(SDL_Window *window); 16 | ~Context(); 17 | 18 | std::string device_name() const; 19 | }; 20 | 21 | Context::Context(SDL_Window *window) 22 | { 23 | // Take the first Metal device 24 | NSArray> *devices = MTLCopyAllDevices(); 25 | if (!devices) { 26 | throw std::runtime_error("No Metal device found!"); 27 | } 28 | device = devices[0]; 29 | [device retain]; 30 | [devices release]; 31 | 32 | SDL_SysWMinfo wm_info; 33 | SDL_VERSION(&wm_info.version); 34 | SDL_GetWindowWMInfo(window, &wm_info); 35 | 36 | // Setup the Metal layer 37 | layer = [CAMetalLayer layer]; 38 | layer.device = device; 39 | layer.pixelFormat = MTLPixelFormatBGRA8Unorm; 40 | layer.framebufferOnly = NO; 41 | 42 | NSWindow *nswindow = wm_info.info.cocoa.window; 43 | nswindow.contentView.layer = layer; 44 | nswindow.contentView.wantsLayer = YES; 45 | } 46 | 47 | Context::~Context() 48 | { 49 | [device release]; 50 | } 51 | 52 | std::string Context::device_name() const 53 | { 54 | return [device.name UTF8String]; 55 | } 56 | 57 | std::shared_ptr make_context(SDL_Window *window) 58 | { 59 | return std::make_shared(window); 60 | } 61 | 62 | wgpu::SurfaceDescriptorFromMetalLayer surface_descriptor(std::shared_ptr &context) 63 | { 64 | wgpu::SurfaceDescriptorFromMetalLayer surf_desc; 65 | surf_desc.layer = context->layer; 66 | return surf_desc; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /arcball_camera.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | /* A simple arcball camera that moves around the camera's focal point. 7 | * The mouse inputs to the camera should be in normalized device coordinates, 8 | * where the top-left of the screen corresponds to [-1, 1], and the bottom 9 | * right is [1, -1]. 10 | */ 11 | class ArcballCamera { 12 | // We store the unmodified look at matrix along with 13 | // decomposed translation and rotation components 14 | glm::mat4 center_translation, translation; 15 | glm::quat rotation; 16 | // camera is the full camera transform, 17 | // inv_camera is stored as well to easily compute 18 | // eye position and world space rotation axes 19 | glm::mat4 camera, inv_camera; 20 | 21 | public: 22 | ArcballCamera() = default; 23 | 24 | /* Create an arcball camera focused on some center point 25 | * screen: [win_width, win_height] 26 | */ 27 | ArcballCamera(const glm::vec3 &eye, const glm::vec3 ¢er, const glm::vec3 &up); 28 | 29 | /* Rotate the camera from the previous mouse position to the current 30 | * one. Mouse positions should be in normalized device coordinates 31 | */ 32 | void rotate(glm::vec2 prev_mouse, glm::vec2 cur_mouse); 33 | 34 | /* Pan the camera given the translation vector. Mouse 35 | * delta amount should be in normalized device coordinates 36 | */ 37 | void pan(glm::vec2 mouse_delta); 38 | 39 | /* Zoom the camera given the zoom amount to (i.e., the scroll amount). 40 | * Positive values zoom in, negative will zoom out. 41 | */ 42 | void zoom(const float zoom_amount); 43 | 44 | // Get the camera transformation matrix 45 | const glm::mat4 &transform() const; 46 | 47 | // Get the camera's inverse transformation matrix 48 | const glm::mat4 &inv_transform() const; 49 | 50 | // Get the eye position of the camera in world space 51 | glm::vec3 eye() const; 52 | 53 | // Get the eye direction of the camera in world space 54 | glm::vec3 dir() const; 55 | 56 | // Get the up direction of the camera in world space 57 | glm::vec3 up() const; 58 | 59 | private: 60 | void update_camera(); 61 | }; 62 | -------------------------------------------------------------------------------- /cmake/FindDawn.cmake: -------------------------------------------------------------------------------- 1 | find_path(DAWN_WGPU_INCLUDE_DIR 2 | NAME dawn/webgpu.h 3 | PATHS 4 | ${Dawn_DIR}/gen/include/ 5 | ) 6 | mark_as_advanced(DAWN_WGPU_INCLUDE_DIR) 7 | 8 | find_path(DAWN_INCLUDE_DIR 9 | NAME dawn/EnumClassBitmasks.h 10 | PATHS 11 | ${Dawn_DIR}/../../include/ 12 | ) 13 | mark_as_advanced(DAWN_INCLUDE_DIR) 14 | 15 | set(DAWN_INCLUDE_DIRS ${DAWN_WGPU_INCLUDE_DIR} ${DAWN_INCLUDE_DIR}) 16 | 17 | find_library(DAWN_NATIVE_LIBRARY 18 | NAMES 19 | dawn_native 20 | # On windows Dawn has the dll in the name 21 | dawn_native.dll.lib 22 | PATHS ${Dawn_DIR} 23 | NO_DEFAULT_PATH 24 | ) 25 | 26 | find_library(DAWN_PLATFORM_LIBRARY 27 | NAMES 28 | dawn_platform 29 | # On windows Dawn has the dll in the name 30 | dawn_platform.dll.lib 31 | PATHS ${Dawn_DIR} 32 | NO_DEFAULT_PATH 33 | ) 34 | 35 | find_library(DAWN_PROC_LIBRARY 36 | NAMES 37 | dawn_proc 38 | # On windows Dawn has the dll in the name 39 | dawn_proc.dll.lib 40 | PATHS ${Dawn_DIR} 41 | NO_DEFAULT_PATH 42 | ) 43 | 44 | set(DAWN_LIBRARIES 45 | ${DAWN_NATIVE_LIBRARY} 46 | ${DAWN_PLATFORM_LIBRARY} 47 | ${DAWN_PROC_LIBRARY}) 48 | 49 | include(FindPackageHandleStandardArgs) 50 | 51 | find_package_handle_standard_args(Dawn REQUIRED_VARS 52 | DAWN_INCLUDE_DIR 53 | DAWN_WGPU_INCLUDE_DIR 54 | DAWN_NATIVE_LIBRARY 55 | DAWN_PLATFORM_LIBRARY 56 | DAWN_PROC_LIBRARY) 57 | 58 | if (Dawn_FOUND AND NOT TARGET Dawn) 59 | add_library(Dawn::Native UNKNOWN IMPORTED) 60 | set_target_properties(Dawn::Native PROPERTIES 61 | IMPORTED_LOCATION 62 | ${DAWN_NATIVE_LIBRARY} 63 | INTERFACE_INCLUDE_DIRECTORIES 64 | "${DAWN_INCLUDE_DIRS}") 65 | target_compile_features(Dawn::Native INTERFACE cxx_std_11) 66 | 67 | add_library(Dawn::Platform UNKNOWN IMPORTED) 68 | set_target_properties(Dawn::Platform PROPERTIES 69 | IMPORTED_LOCATION 70 | ${DAWN_PLATFORM_LIBRARY} 71 | INTERFACE_INCLUDE_DIRECTORIES 72 | "${DAWN_INCLUDE_DIRS}") 73 | target_compile_features(Dawn::Platform INTERFACE cxx_std_11) 74 | 75 | add_library(Dawn::Proc UNKNOWN IMPORTED) 76 | set_target_properties(Dawn::Proc PROPERTIES 77 | IMPORTED_LOCATION 78 | ${DAWN_PROC_LIBRARY} 79 | INTERFACE_INCLUDE_DIRECTORIES 80 | "${DAWN_INCLUDE_DIRS}") 81 | target_compile_features(Dawn::Proc INTERFACE cxx_std_11) 82 | 83 | add_library(Dawn INTERFACE) 84 | target_compile_features(Dawn INTERFACE cxx_std_11) 85 | target_link_libraries(Dawn INTERFACE 86 | Dawn::Native 87 | Dawn::Platform 88 | Dawn::Proc) 89 | 90 | if (APPLE) 91 | message(WARNING 92 | "Make sure you fixed libdawn_native, libdawn_platform and libdawn_proc LC_ID_DYLIB with install_name_tool -id!\n" 93 | "Run: install_name_tool -id \"@rpath/libdawn_.dylib\" libdawn_.dylib") 94 | endif() 95 | endif() 96 | 97 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(wgpu-starter) 3 | 4 | if (NOT WIN32) 5 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic") 6 | endif() 7 | 8 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_LIST_DIR}/cmake") 9 | include(ExternalProject) 10 | 11 | include(cmake/glm.cmake) 12 | 13 | add_definitions(-DGLM_ENABLE_EXPERIMENTAL) 14 | 15 | if (NOT EMSCRIPTEN) 16 | add_definitions(-DNOMINMAX 17 | -DSDL_MAIN_HANDLED 18 | -DWIN32_LEAN_AND_MEAN) 19 | 20 | if (WIN32) 21 | add_definitions(-DDAWN_ENABLE_BACKEND_D3D12) 22 | elseif (APPLE) 23 | add_definitions(-DDAWN_ENABLE_BACKEND_METAL) 24 | endif() 25 | 26 | find_package(Threads REQUIRED) 27 | find_package(SDL2 CONFIG REQUIRED) 28 | find_package(Dawn REQUIRED) 29 | 30 | add_library(webgpu_cpp webgpu_cpp.cpp) 31 | target_link_libraries(webgpu_cpp PUBLIC Dawn) 32 | else() 33 | set(CMAKE_EXE_LINKER_FLAGS "-s USE_WEBGPU=1 -s ALLOW_MEMORY_GROWTH -g-source-map") 34 | # Generate the index.html file that will load our Emscripten compiled module 35 | set(APP_TARGET_NAME wgpu-starter) 36 | configure_file(index.html.in ${CMAKE_CURRENT_BINARY_DIR}/index.html @ONLY) 37 | endif() 38 | 39 | add_executable(wgpu-starter 40 | main.cpp 41 | arcball_camera.cpp) 42 | 43 | set_target_properties(wgpu-starter PROPERTIES 44 | CXX_STANDARD 11 45 | CXX_STANDARD_REQUIRED ON) 46 | 47 | target_link_libraries(wgpu-starter PRIVATE glm) 48 | 49 | if (NOT EMSCRIPTEN) 50 | if (TARGET SDL2::SDL2) 51 | target_link_libraries(wgpu-starter PUBLIC 52 | SDL2::SDL2 53 | SDL2::SDL2main) 54 | else() 55 | target_include_directories(wgpu-starter PUBLIC 56 | $) 57 | target_link_libraries(wgpu-starter PUBLIC ${SDL2_LIBRARIES}) 58 | endif() 59 | 60 | target_link_libraries(wgpu-starter 61 | PUBLIC 62 | webgpu_cpp) 63 | 64 | if (APPLE) 65 | find_library(QUARTZ_CORE QuartzCore) 66 | if (NOT QUARTZ_CORE) 67 | message(FATAL_ERROR "QuartzCore not found") 68 | endif() 69 | 70 | find_library(METAL_LIB Metal) 71 | if (NOT METAL_LIB) 72 | message(FATAL_ERROR "Metal not found") 73 | endif() 74 | 75 | find_library(COCOA_LIB Cocoa) 76 | if (NOT COCOA_LIB) 77 | message(FATAL_ERROR "Cocoa not found") 78 | endif() 79 | 80 | add_library(metal_util metal_util.mm) 81 | 82 | target_link_libraries(metal_util PUBLIC 83 | Dawn 84 | ${QUARTZ_CORE} 85 | ${METAL_LIB} 86 | ${COCOA_LIB}) 87 | 88 | set_target_properties(metal_util PROPERTIES 89 | CXX_STANDARD 11) 90 | 91 | if (TARGET SDL2::SDL2) 92 | target_link_libraries(metal_util PUBLIC 93 | SDL2::SDL2) 94 | else() 95 | target_include_directories(metal_util PUBLIC 96 | $) 97 | target_link_libraries(metal_util PUBLIC ${SDL2_LIBRARIES}) 98 | endif() 99 | 100 | target_link_libraries(wgpu-starter PUBLIC metal_util) 101 | endif() 102 | endif() 103 | -------------------------------------------------------------------------------- /arcball_camera.cpp: -------------------------------------------------------------------------------- 1 | #include "arcball_camera.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | // Project the point in [-1, 1] screen space onto the arcball sphere 8 | static glm::quat screen_to_arcball(const glm::vec2 &p); 9 | 10 | ArcballCamera::ArcballCamera(const glm::vec3 &eye, 11 | const glm::vec3 ¢er, 12 | const glm::vec3 &up) 13 | { 14 | const glm::vec3 dir = center - eye; 15 | glm::vec3 z_axis = glm::normalize(dir); 16 | glm::vec3 x_axis = glm::normalize(glm::cross(z_axis, glm::normalize(up))); 17 | glm::vec3 y_axis = glm::normalize(glm::cross(x_axis, z_axis)); 18 | x_axis = glm::normalize(glm::cross(z_axis, y_axis)); 19 | 20 | center_translation = glm::inverse(glm::translate(center)); 21 | translation = glm::translate(glm::vec3(0.f, 0.f, -glm::length(dir))); 22 | rotation = 23 | glm::normalize(glm::quat_cast(glm::transpose(glm::mat3(x_axis, y_axis, -z_axis)))); 24 | 25 | update_camera(); 26 | } 27 | 28 | void ArcballCamera::rotate(glm::vec2 prev_mouse, glm::vec2 cur_mouse) 29 | { 30 | // Clamp mouse positions to stay in NDC 31 | cur_mouse = glm::clamp(cur_mouse, glm::vec2{-1, -1}, glm::vec2{1, 1}); 32 | prev_mouse = glm::clamp(prev_mouse, glm::vec2{-1, -1}, glm::vec2{1, 1}); 33 | 34 | const glm::quat mouse_cur_ball = screen_to_arcball(cur_mouse); 35 | const glm::quat mouse_prev_ball = screen_to_arcball(prev_mouse); 36 | 37 | rotation = mouse_cur_ball * mouse_prev_ball * rotation; 38 | update_camera(); 39 | } 40 | 41 | void ArcballCamera::pan(glm::vec2 mouse_delta) 42 | { 43 | const float zoom_amount = std::abs(translation[3][2]); 44 | glm::vec4 motion(mouse_delta.x * zoom_amount, mouse_delta.y * zoom_amount, 0.f, 0.f); 45 | // Find the panning amount in the world space 46 | motion = inv_camera * motion; 47 | 48 | center_translation = glm::translate(glm::vec3(motion)) * center_translation; 49 | update_camera(); 50 | } 51 | 52 | void ArcballCamera::zoom(const float zoom_amount) 53 | { 54 | const glm::vec3 motion(0.f, 0.f, zoom_amount); 55 | 56 | translation = glm::translate(motion) * translation; 57 | update_camera(); 58 | } 59 | 60 | const glm::mat4 &ArcballCamera::transform() const 61 | { 62 | return camera; 63 | } 64 | 65 | const glm::mat4 &ArcballCamera::inv_transform() const 66 | { 67 | return inv_camera; 68 | } 69 | 70 | glm::vec3 ArcballCamera::eye() const 71 | { 72 | return glm::vec3{inv_camera * glm::vec4{0, 0, 0, 1}}; 73 | } 74 | 75 | glm::vec3 ArcballCamera::dir() const 76 | { 77 | return glm::normalize(glm::vec3{inv_camera * glm::vec4{0, 0, -1, 0}}); 78 | } 79 | 80 | glm::vec3 ArcballCamera::up() const 81 | { 82 | return glm::normalize(glm::vec3{inv_camera * glm::vec4{0, 1, 0, 0}}); 83 | } 84 | 85 | void ArcballCamera::update_camera() 86 | { 87 | camera = translation * glm::mat4_cast(rotation) * center_translation; 88 | inv_camera = glm::inverse(camera); 89 | } 90 | 91 | glm::quat screen_to_arcball(const glm::vec2 &p) 92 | { 93 | const float dist = glm::dot(p, p); 94 | // If we're on/in the sphere return the point on it 95 | if (dist <= 1.f) { 96 | return glm::quat(0.0, p.x, p.y, std::sqrt(1.f - dist)); 97 | } else { 98 | // otherwise we project the point onto the sphere 99 | const glm::vec2 proj = glm::normalize(p); 100 | return glm::quat(0.0, proj.x, proj.y, 0.f); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | AccessModifierOffset: -4 4 | AlignAfterOpenBracket: Align 5 | AlignConsecutiveAssignments: false 6 | AlignConsecutiveDeclarations: false 7 | AlignEscapedNewlinesLeft: true 8 | AlignOperands: true 9 | AlignTrailingComments: true 10 | AllowAllParametersOfDeclarationOnNextLine: true 11 | AllowShortBlocksOnASingleLine: false 12 | AllowShortCaseLabelsOnASingleLine: false 13 | AllowShortFunctionsOnASingleLine: Empty 14 | AllowShortIfStatementsOnASingleLine: false 15 | AllowShortLoopsOnASingleLine: false 16 | AlwaysBreakAfterDefinitionReturnType: None 17 | AlwaysBreakAfterReturnType: None 18 | AlwaysBreakBeforeMultilineStrings: true 19 | AlwaysBreakTemplateDeclarations: true 20 | BinPackArguments: false 21 | BinPackParameters: false 22 | BraceWrapping: 23 | AfterClass: false 24 | AfterControlStatement: false 25 | AfterEnum: false 26 | AfterFunction: true 27 | AfterNamespace: false 28 | AfterStruct: false 29 | AfterUnion: false 30 | AfterExternBlock: false 31 | BeforeCatch: false 32 | BeforeElse: false 33 | IndentBraces: false 34 | BreakBeforeBinaryOperators: None 35 | BreakBeforeBraces: Custom 36 | BreakBeforeTernaryOperators: true 37 | BreakConstructorInitializersBeforeComma: false 38 | BreakStringLiterals: true 39 | ColumnLimit: 95 40 | CommentPragmas: '^ IWYU pragma:' 41 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 42 | Cpp11BracedListStyle: true 43 | DerivePointerAlignment: false 44 | DisableFormat: false 45 | ExperimentalAutoDetectBinPacking: false 46 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 47 | IncludeCategories: 48 | - Regex: '^<[^\.]*>' 49 | Priority: 1 50 | - Regex: '^<.*\.h>' 51 | Priority: 2 52 | - Regex: '.*' 53 | Priority: 3 54 | SortIncludes: true 55 | ConstructorInitializerIndentWidth: 4 56 | ContinuationIndentWidth: 4 57 | IndentCaseLabels: false 58 | IndentWidth: 4 59 | IndentWrappedFunctionNames: false 60 | KeepEmptyLinesAtTheStartOfBlocks: false 61 | MacroBlockBegin: '' 62 | MacroBlockEnd: '' 63 | MaxEmptyLinesToKeep: 1 64 | NamespaceIndentation: Inner 65 | PenaltyBreakBeforeFirstCallParameter: 1 66 | PenaltyBreakComment: 300 67 | PenaltyBreakFirstLessLess: 120 68 | PenaltyBreakString: 1000 69 | PenaltyExcessCharacter: 1000000 70 | PenaltyReturnTypeOnItsOwnLine: 200 71 | PointerAlignment: Right 72 | ReflowComments: true 73 | SpaceAfterCStyleCast: false 74 | SpaceAfterTemplateKeyword: true 75 | SpaceBeforeAssignmentOperators: true 76 | SpaceBeforeParens: ControlStatements 77 | SpaceInEmptyParentheses: false 78 | SpacesBeforeTrailingComments: 2 79 | SpacesInAngles: false 80 | SpacesInContainerLiterals: false 81 | SpacesInCStyleCastParentheses: false 82 | SpacesInParentheses: false 83 | SpacesInSquareBrackets: false 84 | Standard: Cpp11 85 | TabWidth: 4 86 | FixNamespaceComments: false 87 | UseTab: Never 88 | ... 89 | --- 90 | Language: ObjC 91 | AccessModifierOffset: -4 92 | AlignAfterOpenBracket: Align 93 | AlignConsecutiveAssignments: false 94 | AlignConsecutiveDeclarations: false 95 | AlignEscapedNewlinesLeft: true 96 | AlignOperands: true 97 | AlignTrailingComments: true 98 | AllowAllParametersOfDeclarationOnNextLine: true 99 | AllowShortBlocksOnASingleLine: false 100 | AllowShortCaseLabelsOnASingleLine: false 101 | AllowShortFunctionsOnASingleLine: Empty 102 | AllowShortIfStatementsOnASingleLine: false 103 | AllowShortLoopsOnASingleLine: false 104 | AlwaysBreakAfterDefinitionReturnType: None 105 | AlwaysBreakAfterReturnType: None 106 | AlwaysBreakBeforeMultilineStrings: true 107 | AlwaysBreakTemplateDeclarations: true 108 | BinPackArguments: false 109 | BinPackParameters: false 110 | BraceWrapping: 111 | AfterClass: false 112 | AfterControlStatement: false 113 | AfterEnum: false 114 | AfterFunction: true 115 | AfterNamespace: false 116 | AfterStruct: false 117 | AfterUnion: false 118 | AfterExternBlock: false 119 | BeforeCatch: false 120 | BeforeElse: false 121 | IndentBraces: false 122 | BreakBeforeBinaryOperators: None 123 | BreakBeforeBraces: Custom 124 | BreakBeforeTernaryOperators: true 125 | BreakConstructorInitializersBeforeComma: false 126 | BreakStringLiterals: true 127 | ColumnLimit: 95 128 | CommentPragmas: '^ IWYU pragma:' 129 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 130 | Cpp11BracedListStyle: true 131 | DerivePointerAlignment: false 132 | DisableFormat: false 133 | ExperimentalAutoDetectBinPacking: false 134 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 135 | IncludeCategories: 136 | - Regex: '^<[^\.]*>' 137 | Priority: 1 138 | - Regex: '^<.*\.h>' 139 | Priority: 2 140 | - Regex: '.*' 141 | Priority: 3 142 | SortIncludes: true 143 | ConstructorInitializerIndentWidth: 4 144 | ContinuationIndentWidth: 4 145 | IndentCaseLabels: false 146 | IndentWidth: 4 147 | IndentWrappedFunctionNames: false 148 | KeepEmptyLinesAtTheStartOfBlocks: false 149 | MacroBlockBegin: '' 150 | MacroBlockEnd: '' 151 | MaxEmptyLinesToKeep: 1 152 | NamespaceIndentation: Inner 153 | PenaltyBreakBeforeFirstCallParameter: 1 154 | PenaltyBreakComment: 300 155 | PenaltyBreakFirstLessLess: 120 156 | PenaltyBreakString: 1000 157 | PenaltyExcessCharacter: 1000000 158 | PenaltyReturnTypeOnItsOwnLine: 200 159 | PointerAlignment: Right 160 | ReflowComments: true 161 | SpaceAfterCStyleCast: false 162 | SpaceAfterTemplateKeyword: true 163 | SpaceBeforeAssignmentOperators: true 164 | SpaceBeforeParens: ControlStatements 165 | SpaceInEmptyParentheses: false 166 | SpacesBeforeTrailingComments: 2 167 | SpacesInAngles: false 168 | SpacesInContainerLiterals: false 169 | SpacesInCStyleCastParentheses: false 170 | SpacesInParentheses: false 171 | SpacesInSquareBrackets: false 172 | Standard: Cpp11 173 | TabWidth: 4 174 | FixNamespaceComments: false 175 | UseTab: Never 176 | ... 177 | 178 | 179 | 180 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "arcball_camera.h" 7 | #include 8 | #include 9 | 10 | #ifdef __EMSCRIPTEN__ 11 | #include 12 | #include 13 | #include 14 | // TODO: Maybe change to use the plain C API? 15 | #include 16 | #else 17 | #include 18 | #include 19 | 20 | // Note: include order does matter on Linux 21 | #include 22 | #include 23 | 24 | #if defined(_WIN32) 25 | #include 26 | #elif defined(__APPLE__) 27 | #include 28 | #include "metal_util.h" 29 | #else 30 | #include 31 | #endif 32 | 33 | #endif 34 | 35 | const std::string WGSL_SHADER = R"( 36 | alias float4 = vec4; 37 | 38 | struct VertexInput { 39 | @location(0) position: float4, 40 | @location(1) color: float4, 41 | }; 42 | 43 | struct VertexOutput { 44 | @builtin(position) position: float4, 45 | @location(0) color: float4, 46 | }; 47 | 48 | struct ViewParams { 49 | view_proj: mat4x4, 50 | }; 51 | 52 | @group(0) @binding(0) 53 | var view_params: ViewParams; 54 | 55 | @vertex 56 | fn vertex_main(vert: VertexInput) -> VertexOutput { 57 | var out: VertexOutput; 58 | out.color = vert.color; 59 | out.position = view_params.view_proj * vert.position; 60 | return out; 61 | }; 62 | 63 | @fragment 64 | fn fragment_main(in: VertexOutput) -> @location(0) float4 { 65 | return float4(in.color); 66 | } 67 | )"; 68 | 69 | #ifndef __EMSCRIPTEN__ 70 | dawn::native::Adapter request_adapter(dawn::native::Instance &instance, 71 | const wgpu::BackendType backend_type) 72 | { 73 | std::vector adapters = instance.EnumerateAdapters(); 74 | for (const auto &a : adapters) { 75 | wgpu::AdapterProperties props; 76 | a.GetProperties(&props); 77 | std::cout << "Adapter name: " << props.name 78 | << ", driver desc: " << props.driverDescription << "\n"; 79 | if (props.backendType == backend_type) { 80 | return a; 81 | } 82 | } 83 | throw std::runtime_error("No suitable adapter found!"); 84 | } 85 | #endif 86 | 87 | struct AppState { 88 | wgpu::Device device; 89 | wgpu::Queue queue; 90 | 91 | wgpu::Surface surface; 92 | wgpu::SwapChain swap_chain; 93 | wgpu::RenderPipeline render_pipeline; 94 | wgpu::Buffer vertex_buf; 95 | wgpu::Buffer view_param_buf; 96 | wgpu::BindGroup bind_group; 97 | 98 | ArcballCamera camera; 99 | glm::mat4 proj; 100 | 101 | bool done = false; 102 | bool camera_changed = true; 103 | glm::vec2 prev_mouse = glm::vec2(-2.f); 104 | }; 105 | 106 | int win_width = 640; 107 | int win_height = 480; 108 | 109 | glm::vec2 transform_mouse(glm::vec2 in) 110 | { 111 | return glm::vec2(in.x * 2.f / win_width - 1.f, 1.f - 2.f * in.y / win_height); 112 | } 113 | 114 | void loop_iteration(void *_app_state); 115 | 116 | int main(int argc, const char **argv) 117 | { 118 | AppState *app_state = new AppState; 119 | 120 | #ifdef __EMSCRIPTEN__ 121 | app_state->device = wgpu::Device::Acquire(emscripten_webgpu_get_device()); 122 | 123 | wgpu::InstanceDescriptor instance_desc; 124 | wgpu::Instance instance = wgpu::CreateInstance(&instance_desc); 125 | #else 126 | dawn::native::Instance dawn_instance; 127 | 128 | #if defined(_WIN32) 129 | const auto backend_type = wgpu::BackendType::D3D12; 130 | #elif defined(__APPLE__) 131 | const auto backend_type = wgpu::BackendType::Metal; 132 | #else 133 | const auto backend_type = wgpu::BackendType::Vulkan; 134 | #endif 135 | 136 | auto adapter = request_adapter(dawn_instance, backend_type); 137 | DawnProcTable procs(dawn::native::GetProcs()); 138 | dawnProcSetProcs(&procs); 139 | 140 | wgpu::Instance instance = dawn_instance.Get(); 141 | app_state->device = wgpu::Device::Acquire(adapter.CreateDevice()); 142 | 143 | SDL_Window *window = SDL_CreateWindow("wgpu-starter", 144 | SDL_WINDOWPOS_CENTERED, 145 | SDL_WINDOWPOS_CENTERED, 146 | win_width, 147 | win_height, 148 | 0); 149 | 150 | #endif 151 | app_state->device.SetUncapturedErrorCallback( 152 | [](WGPUErrorType type, const char *msg, void *data) { 153 | std::cout << "WebGPU Error: " << msg << "\n" << std::flush; 154 | std::exit(1); 155 | }, 156 | nullptr); 157 | 158 | /* 159 | app_state->device.SetLoggingCallback( 160 | [](WGPULoggingType type, const char *msg, void *data) { 161 | std::cout << "WebGPU Log: " << msg << "\n" << std::flush; 162 | }, 163 | nullptr); 164 | */ 165 | 166 | app_state->queue = app_state->device.GetQueue(); 167 | 168 | #ifdef __EMSCRIPTEN__ 169 | wgpu::SurfaceDescriptorFromCanvasHTMLSelector selector; 170 | selector.selector = "#webgpu-canvas"; 171 | 172 | wgpu::SurfaceDescriptor surface_desc; 173 | surface_desc.nextInChain = &selector; 174 | #else 175 | 176 | // Setup the swap chain for the native API 177 | #if defined(_WIN32) 178 | SDL_SysWMinfo wm_info; 179 | SDL_VERSION(&wm_info.version); 180 | SDL_GetWindowWMInfo(window, &wm_info); 181 | 182 | wgpu::SurfaceDescriptorFromWindowsHWND native_surf; 183 | native_surf.hwnd = wm_info.info.win.window; 184 | native_surf.hinstance = wm_info.info.win.hinstance; 185 | #elif defined(__APPLE__) 186 | auto metal_context = metal::make_context(window); 187 | wgpu::SurfaceDescriptorFromMetalLayer native_surf = 188 | metal::surface_descriptor(metal_context); 189 | #else 190 | SDL_SysWMinfo wm_info; 191 | SDL_VERSION(&wm_info.version); 192 | SDL_GetWindowWMInfo(window, &wm_info); 193 | 194 | // TODO Later: Maybe set up a native Vulkan swap chain instead, a bit more work but 195 | // may avoid possible XWayland compatibility issues since it can then run natively on 196 | // Wayland 197 | wgpu::SurfaceDescriptorFromXlib native_surf; 198 | native_surf.display = wm_info.info.x11.display; 199 | native_surf.window = wm_info.info.x11.window; 200 | #endif 201 | 202 | wgpu::SurfaceDescriptor surface_desc; 203 | surface_desc.nextInChain = &native_surf; 204 | #endif 205 | 206 | app_state->surface = instance.CreateSurface(&surface_desc); 207 | 208 | wgpu::SwapChainDescriptor swap_chain_desc; 209 | swap_chain_desc.format = wgpu::TextureFormat::BGRA8Unorm; 210 | swap_chain_desc.usage = wgpu::TextureUsage::RenderAttachment; 211 | swap_chain_desc.presentMode = wgpu::PresentMode::Fifo; 212 | swap_chain_desc.width = win_width; 213 | swap_chain_desc.height = win_height; 214 | 215 | app_state->swap_chain = 216 | app_state->device.CreateSwapChain(app_state->surface, &swap_chain_desc); 217 | 218 | wgpu::ShaderModule shader_module; 219 | { 220 | wgpu::ShaderModuleWGSLDescriptor shader_module_wgsl; 221 | shader_module_wgsl.code = WGSL_SHADER.c_str(); 222 | 223 | wgpu::ShaderModuleDescriptor shader_module_desc; 224 | shader_module_desc.nextInChain = &shader_module_wgsl; 225 | shader_module = app_state->device.CreateShaderModule(&shader_module_desc); 226 | 227 | // TODO: Status always seems to be success even when there are errors? 228 | // Unimplemented on Emscripten? Did the name change? 229 | /* 230 | shader_module.GetCompilationInfo( 231 | [](WGPUCompilationInfoRequestStatus status, 232 | WGPUCompilationInfo const *info, 233 | void *) { 234 | if (info->messageCount != 0) { 235 | std::cout << "Shader compilation info:\n"; 236 | for (uint32_t i = 0; i < info->messageCount; ++i) { 237 | const auto &m = info->messages[i]; 238 | std::cout << m.lineNum << ":" << m.linePos << ": "; 239 | switch (m.type) { 240 | case WGPUCompilationMessageType_Error: 241 | std::cout << "error"; 242 | break; 243 | case WGPUCompilationMessageType_Warning: 244 | std::cout << "warning"; 245 | break; 246 | case WGPUCompilationMessageType_Info: 247 | std::cout << "info"; 248 | break; 249 | case WGPUCompilationMessageType_Force32: 250 | std::cout << "force32"; 251 | break; 252 | default: 253 | break; 254 | } 255 | 256 | std::cout << ": " << m.message << "\n"; 257 | } 258 | } 259 | }, 260 | nullptr); 261 | */ 262 | } 263 | 264 | // Upload vertex data 265 | const std::vector vertex_data = { 266 | 1, -1, 0, 1, // position 267 | 1, 0, 0, 1, // color 268 | -1, -1, 0, 1, // position 269 | 0, 1, 0, 1, // color 270 | 0, 1, 0, 1, // position 271 | 0, 0, 1, 1, // color 272 | }; 273 | wgpu::BufferDescriptor buffer_desc; 274 | buffer_desc.mappedAtCreation = true; 275 | buffer_desc.size = vertex_data.size() * sizeof(float); 276 | buffer_desc.usage = wgpu::BufferUsage::Vertex; 277 | app_state->vertex_buf = app_state->device.CreateBuffer(&buffer_desc); 278 | std::memcpy(app_state->vertex_buf.GetMappedRange(), vertex_data.data(), buffer_desc.size); 279 | app_state->vertex_buf.Unmap(); 280 | 281 | std::array vertex_attributes; 282 | vertex_attributes[0].format = wgpu::VertexFormat::Float32x4; 283 | vertex_attributes[0].offset = 0; 284 | vertex_attributes[0].shaderLocation = 0; 285 | 286 | vertex_attributes[1].format = wgpu::VertexFormat::Float32x4; 287 | vertex_attributes[1].offset = 4 * 4; 288 | vertex_attributes[1].shaderLocation = 1; 289 | 290 | wgpu::VertexBufferLayout vertex_buf_layout; 291 | vertex_buf_layout.arrayStride = 2 * 4 * 4; 292 | vertex_buf_layout.attributeCount = vertex_attributes.size(); 293 | vertex_buf_layout.attributes = vertex_attributes.data(); 294 | 295 | wgpu::VertexState vertex_state; 296 | vertex_state.module = shader_module; 297 | vertex_state.entryPoint = "vertex_main"; 298 | vertex_state.bufferCount = 1; 299 | vertex_state.buffers = &vertex_buf_layout; 300 | 301 | wgpu::ColorTargetState render_target_state; 302 | render_target_state.format = wgpu::TextureFormat::BGRA8Unorm; 303 | 304 | wgpu::FragmentState fragment_state; 305 | fragment_state.module = shader_module; 306 | fragment_state.entryPoint = "fragment_main"; 307 | fragment_state.targetCount = 1; 308 | fragment_state.targets = &render_target_state; 309 | 310 | wgpu::BindGroupLayoutEntry view_param_layout_entry = {}; 311 | view_param_layout_entry.binding = 0; 312 | view_param_layout_entry.buffer.hasDynamicOffset = false; 313 | view_param_layout_entry.buffer.type = wgpu::BufferBindingType::Uniform; 314 | view_param_layout_entry.visibility = wgpu::ShaderStage::Vertex; 315 | 316 | wgpu::BindGroupLayoutDescriptor view_params_bg_layout_desc = {}; 317 | view_params_bg_layout_desc.entryCount = 1; 318 | view_params_bg_layout_desc.entries = &view_param_layout_entry; 319 | 320 | wgpu::BindGroupLayout view_params_bg_layout = 321 | app_state->device.CreateBindGroupLayout(&view_params_bg_layout_desc); 322 | 323 | wgpu::PipelineLayoutDescriptor pipeline_layout_desc = {}; 324 | pipeline_layout_desc.bindGroupLayoutCount = 1; 325 | pipeline_layout_desc.bindGroupLayouts = &view_params_bg_layout; 326 | 327 | wgpu::PipelineLayout pipeline_layout = 328 | app_state->device.CreatePipelineLayout(&pipeline_layout_desc); 329 | 330 | wgpu::RenderPipelineDescriptor render_pipeline_desc; 331 | render_pipeline_desc.vertex = vertex_state; 332 | render_pipeline_desc.fragment = &fragment_state; 333 | render_pipeline_desc.layout = pipeline_layout; 334 | // Default primitive state is what we want, triangle list, no indices 335 | 336 | app_state->render_pipeline = app_state->device.CreateRenderPipeline(&render_pipeline_desc); 337 | 338 | // Create the UBO for our bind group 339 | wgpu::BufferDescriptor ubo_buffer_desc; 340 | ubo_buffer_desc.mappedAtCreation = false; 341 | ubo_buffer_desc.size = 16 * sizeof(float); 342 | ubo_buffer_desc.usage = wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopyDst; 343 | app_state->view_param_buf = app_state->device.CreateBuffer(&ubo_buffer_desc); 344 | 345 | wgpu::BindGroupEntry view_param_bg_entry = {}; 346 | view_param_bg_entry.binding = 0; 347 | view_param_bg_entry.buffer = app_state->view_param_buf; 348 | view_param_bg_entry.size = ubo_buffer_desc.size; 349 | 350 | wgpu::BindGroupDescriptor bind_group_desc = {}; 351 | bind_group_desc.layout = view_params_bg_layout; 352 | bind_group_desc.entryCount = 1; 353 | bind_group_desc.entries = &view_param_bg_entry; 354 | 355 | app_state->bind_group = app_state->device.CreateBindGroup(&bind_group_desc); 356 | 357 | app_state->proj = glm::perspective( 358 | glm::radians(50.f), static_cast(win_width) / win_height, 0.1f, 100.f); 359 | app_state->camera = ArcballCamera(glm::vec3(0, 0, -2.5), glm::vec3(0), glm::vec3(0, 1, 0)); 360 | 361 | #ifdef __EMSCRIPTEN__ 362 | emscripten_set_main_loop_arg(loop_iteration, app_state, -1, 0); 363 | #else 364 | while (!app_state->done) { 365 | loop_iteration(app_state); 366 | } 367 | SDL_DestroyWindow(window); 368 | #endif 369 | return 0; 370 | } 371 | 372 | #ifdef __EMSCRIPTEN__ 373 | int mouse_move_callback(int type, const EmscriptenMouseEvent *event, void *_app_state) 374 | { 375 | AppState *app_state = reinterpret_cast(_app_state); 376 | 377 | const glm::vec2 cur_mouse = transform_mouse(glm::vec2(event->clientX, event->clientY)); 378 | 379 | if (app_state->prev_mouse != glm::vec2(-2.f)) { 380 | if (event->buttons & 1) { 381 | app_state->camera.rotate(app_state->prev_mouse, cur_mouse); 382 | app_state->camera_changed = true; 383 | } else if (event->buttons & 2) { 384 | app_state->camera.pan(cur_mouse - app_state->prev_mouse); 385 | app_state->camera_changed = true; 386 | } 387 | } 388 | app_state->prev_mouse = cur_mouse; 389 | 390 | return true; 391 | } 392 | 393 | int mouse_wheel_callback(int type, const EmscriptenWheelEvent *event, void *_app_state) 394 | { 395 | AppState *app_state = reinterpret_cast(_app_state); 396 | 397 | app_state->camera.zoom(event->deltaY * 0.00005f); 398 | app_state->camera_changed = true; 399 | return true; 400 | } 401 | #endif 402 | 403 | void loop_iteration(void *_app_state) 404 | { 405 | AppState *app_state = reinterpret_cast(_app_state); 406 | #ifndef __EMSCRIPTEN__ 407 | SDL_Event event; 408 | // TODO: Because I don't make the window/canvas with SDL_CreateWindow 409 | // it won't attach the listeners properly to get events with SDL. 410 | // So I need to use the Emscripten HTML5 input API to get events instead 411 | while (SDL_PollEvent(&event)) { 412 | if (event.type == SDL_QUIT) { 413 | app_state->done = true; 414 | } 415 | if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE) { 416 | app_state->done = true; 417 | } 418 | if (event.type == SDL_MOUSEMOTION) { 419 | const glm::vec2 cur_mouse = 420 | transform_mouse(glm::vec2(event.motion.x, event.motion.y)); 421 | if (app_state->prev_mouse != glm::vec2(-2.f)) { 422 | if (event.motion.state & SDL_BUTTON_LMASK) { 423 | app_state->camera.rotate(app_state->prev_mouse, cur_mouse); 424 | app_state->camera_changed = true; 425 | } else if (event.motion.state & SDL_BUTTON_RMASK) { 426 | app_state->camera.pan(cur_mouse - app_state->prev_mouse); 427 | app_state->camera_changed = true; 428 | } 429 | } 430 | app_state->prev_mouse = cur_mouse; 431 | } 432 | if (event.type == SDL_MOUSEWHEEL) { 433 | app_state->camera.zoom(event.wheel.y * 0.05f); 434 | app_state->camera_changed = true; 435 | } 436 | } 437 | #else 438 | emscripten_set_mousemove_callback("#webgpu-canvas", app_state, true, mouse_move_callback); 439 | emscripten_set_wheel_callback("#webgpu-canvas", app_state, true, mouse_wheel_callback); 440 | #endif 441 | 442 | wgpu::Buffer upload_buf; 443 | if (app_state->camera_changed) { 444 | wgpu::BufferDescriptor upload_buffer_desc; 445 | upload_buffer_desc.mappedAtCreation = true; 446 | upload_buffer_desc.size = 16 * sizeof(float); 447 | upload_buffer_desc.usage = wgpu::BufferUsage::CopySrc; 448 | upload_buf = app_state->device.CreateBuffer(&upload_buffer_desc); 449 | 450 | const glm::mat4 proj_view = app_state->proj * app_state->camera.transform(); 451 | 452 | std::memcpy( 453 | upload_buf.GetMappedRange(), glm::value_ptr(proj_view), 16 * sizeof(float)); 454 | upload_buf.Unmap(); 455 | } 456 | 457 | wgpu::RenderPassColorAttachment color_attachment; 458 | color_attachment.view = app_state->swap_chain.GetCurrentTextureView(); 459 | color_attachment.clearValue.r = 0.f; 460 | color_attachment.clearValue.g = 0.f; 461 | color_attachment.clearValue.b = 0.f; 462 | color_attachment.clearValue.a = 1.f; 463 | color_attachment.loadOp = wgpu::LoadOp::Clear; 464 | color_attachment.storeOp = wgpu::StoreOp::Store; 465 | 466 | wgpu::RenderPassDescriptor pass_desc; 467 | pass_desc.colorAttachmentCount = 1; 468 | pass_desc.colorAttachments = &color_attachment; 469 | 470 | wgpu::CommandEncoder encoder = app_state->device.CreateCommandEncoder(); 471 | if (app_state->camera_changed) { 472 | encoder.CopyBufferToBuffer( 473 | upload_buf, 0, app_state->view_param_buf, 0, 16 * sizeof(float)); 474 | } 475 | 476 | wgpu::RenderPassEncoder render_pass_enc = encoder.BeginRenderPass(&pass_desc); 477 | render_pass_enc.SetPipeline(app_state->render_pipeline); 478 | render_pass_enc.SetVertexBuffer(0, app_state->vertex_buf); 479 | render_pass_enc.SetBindGroup(0, app_state->bind_group); 480 | render_pass_enc.Draw(3); 481 | render_pass_enc.End(); 482 | 483 | wgpu::CommandBuffer commands = encoder.Finish(); 484 | // Here the # refers to the number of command buffers being submitted 485 | app_state->queue.Submit(1, &commands); 486 | 487 | #ifndef __EMSCRIPTEN__ 488 | app_state->swap_chain.Present(); 489 | #endif 490 | app_state->camera_changed = false; 491 | } 492 | --------------------------------------------------------------------------------