게시일: 2023년 7월 20일, 최종 업데이트: 2025년 8월 27일
웹 개발자에게 WebGPU는 GPU에 통합되고 빠른 액세스를 제공하는 웹 그래픽 API입니다. WebGPU는 최신 하드웨어 기능을 노출하고 Direct3D 12, Metal, Vulkan과 유사하게 GPU에서 렌더링 및 컴퓨팅 작업을 허용합니다.
사실이긴 하지만 이 이야기는 불완전합니다. WebGPU는 Apple, Google, Intel, Mozilla, Microsoft와 같은 주요 기업을 비롯한 공동 노력의 결과입니다. 이 중 일부는 WebGPU가 JavaScript API 이상으로 웹 이외의 생태계에 있는 개발자를 위한 크로스 플랫폼 그래픽 API가 될 수 있다고 인식했습니다.
기본 사용 사례를 충족하기 위해 Chrome 113에서 JavaScript API가 도입되었습니다. 하지만 이와 함께 또 다른 중요한 프로젝트인 webgpu.h C API가 개발되었습니다. 이 C 헤더 파일에는 WebGPU의 사용 가능한 모든 프로시저와 데이터 구조가 나열되어 있습니다. 이는 플랫폼에 구애받지 않는 하드웨어 추상화 계층 역할을 하여 다양한 플랫폼에서 일관된 인터페이스를 제공함으로써 플랫폼별 애플리케이션을 빌드할 수 있도록 지원합니다.
이 문서에서는 웹과 특정 플랫폼 모두에서 실행되는 WebGPU를 사용하여 작은 C++ 앱을 작성하는 방법을 알아봅니다. 스포일러를 하자면, 코드베이스를 최소한으로 조정하여 브라우저 창과 데스크톱 창에 표시되는 것과 동일한 빨간색 삼각형을 얻을 수 있습니다.

기본 원리
완성된 애플리케이션을 보려면 WebGPU 교차 플랫폼 앱 저장소를 확인하세요.
이 앱은 WebGPU를 사용하여 단일 코드베이스에서 데스크톱 및 웹 앱을 빌드하는 방법을 보여주는 최소한의 C++ 예시입니다. 내부적으로는 WebGPU의 webgpu.h를 webgpu_cpp.h라는 C++ 래퍼를 통해 플랫폼에 구애받지 않는 하드웨어 추상화 레이어로 사용합니다.
웹에서 앱은 JavaScript API 위에 webgpu.h를 구현하는 바인딩이 있는 emdawnwebgpu (Emscripten Dawn WebGPU)에 대해 빌드됩니다. macOS 또는 Windows와 같은 특정 플랫폼에서는 이 프로젝트를 Chromium의 크로스 플랫폼 WebGPU 구현인 Dawn에 대해 빌드할 수 있습니다. webgpu.h의 Rust 구현인 wgpu-native도 있지만 이 문서에서는 사용하지 않습니다.
시작하기
시작하려면 표준 방식으로 크로스 플랫폼 빌드를 처리할 C++ 컴파일러와 CMake가 필요합니다. 전용 폴더 내에 main.cpp
소스 파일과 CMakeLists.txt
빌드 파일을 만듭니다.
main.cpp
파일에는 현재 빈 main()
함수가 포함되어야 합니다.
int main() {}
CMakeLists.txt
파일에는 프로젝트에 관한 기본 정보가 포함됩니다. 마지막 줄은 실행 파일 이름이 'app'이고 소스 코드가 main.cpp
임을 지정합니다.
cmake_minimum_required(VERSION 3.22) # CMake version check
project(app) # Create project "app"
set(CMAKE_CXX_STANDARD 20) # Enable C++20 standard
add_executable(app "main.cpp")
cmake -B build
을 실행하여 'build/' 하위 폴더에 빌드 파일을 만들고 cmake --build build
을 실행하여 실제로 앱을 빌드하고 실행 파일을 생성합니다.
# Build the app with CMake.
$ cmake -B build && cmake --build build
# Run the app.
$ ./build/app
앱이 실행되지만 화면에 항목을 그릴 방법이 필요하므로 아직 출력은 없습니다.
Get Dawn
삼각형을 그리려면 Chromium의 크로스 플랫폼 WebGPU 구현인 Dawn을 활용하면 됩니다. 여기에는 화면에 그리기 위한 GLFW C++ 라이브러리가 포함됩니다. Dawn을 다운로드하는 한 가지 방법은 저장소에 git 하위 모듈로 추가하는 것입니다. 다음 명령어는 'dawn/' 하위 폴더에서 가져옵니다.
$ git init
$ git submodule add https://dawn.googlesource.com/dawn
그런 다음 다음과 같이 CMakeLists.txt
파일에 추가합니다.
- CMake
DAWN_FETCH_DEPENDENCIES
옵션은 모든 Dawn 종속 항목을 가져옵니다. - CMake
DAWN_BUILD_MONOLITHIC_LIBRARY
옵션은 모든 Dawn 구성요소를 단일 라이브러리로 번들링합니다. dawn/
하위 폴더가 타겟에 포함됩니다.- 나중에
main.cpp
파일에서 사용할 수 있도록 앱이webgpu_dawn
,webgpu_glfw
,glfw
타겟에 종속됩니다.
…
set(DAWN_FETCH_DEPENDENCIES ON)
set(DAWN_BUILD_MONOLITHIC_LIBRARY STATIC)
add_subdirectory("dawn" EXCLUDE_FROM_ALL)
target_link_libraries(app PRIVATE webgpu_dawn webgpu_glfw glfw)
창 열기
이제 Dawn을 사용할 수 있으므로 GLFW를 사용하여 화면에 항목을 그립니다. 편의를 위해 webgpu_glfw
에 포함된 이 라이브러리를 사용하면 창 관리를 위해 플랫폼에 구애받지 않는 코드를 작성할 수 있습니다.
해상도가 512x512인 'WebGPU window'라는 창을 열려면 main.cpp
파일을 아래와 같이 업데이트합니다. 여기서는 특정 그래픽 API 초기화를 요청하지 않기 위해 glfwWindowHint()
가 사용됩니다.
#include <GLFW/glfw3.h>
const uint32_t kWidth = 512;
const uint32_t kHeight = 512;
void Start() {
if (!glfwInit()) {
return;
}
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
GLFWwindow* window =
glfwCreateWindow(kWidth, kHeight, "WebGPU window", nullptr, nullptr);
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
// TODO: Render a triangle using WebGPU.
}
}
int main() {
Start();
}
이제 앱을 다시 빌드하고 이전과 같이 실행하면 빈 창이 표시됩니다. 진전을 보이고 있습니다.

GPU 기기 가져오기
JavaScript에서 navigator.gpu
는 GPU에 액세스하기 위한 진입점입니다. C++에서는 동일한 용도로 사용되는 wgpu::Instance
변수를 수동으로 만들어야 합니다. 편의를 위해 main.cpp
파일 상단에서 instance
을 선언하고 Init()
내에서 wgpu::CreateInstance()
을 호출합니다.
#include <webgpu/webgpu_cpp.h>
…
wgpu::Instance instance;
…
void Init() {
static const auto kTimedWaitAny = wgpu::InstanceFeatureName::TimedWaitAny;
wgpu::InstanceDescriptor instanceDesc{.requiredFeatureCount = 1,
.requiredFeatures = &kTimedWaitAny};
instance = wgpu::CreateInstance(&instanceDesc);
}
int main() {
Init();
Start();
}
main.cpp
파일 상단에서 두 변수 wgpu::Adapter
와 wgpu::Device
를 선언합니다. instance.RequestAdapter()
를 호출하고 결과 콜백을 adapter
에 할당한 다음 adapter.RequestDevice()
를 호출하고 결과 콜백을 device
에 할당하도록 Init()
함수를 업데이트합니다.
#include <iostream>
#include <dawn/webgpu_cpp_print.h>
…
wgpu::Adapter adapter;
wgpu::Device device;
void Init() {
…
wgpu::Future f1 = instance.RequestAdapter(
nullptr, wgpu::CallbackMode::WaitAnyOnly,
[](wgpu::RequestAdapterStatus status, wgpu::Adapter a,
wgpu::StringView message) {
if (status != wgpu::RequestAdapterStatus::Success) {
std::cout << "RequestAdapter: " << message << "\n";
exit(0);
}
adapter = std::move(a);
});
instance.WaitAny(f1, UINT64_MAX);
wgpu::DeviceDescriptor desc{};
desc.SetUncapturedErrorCallback([](const wgpu::Device&,
wgpu::ErrorType errorType,
wgpu::StringView message) {
std::cout << "Error: " << errorType << " - message: " << message << "\n";
});
wgpu::Future f2 = adapter.RequestDevice(
&desc, wgpu::CallbackMode::WaitAnyOnly,
[](wgpu::RequestDeviceStatus status, wgpu::Device d,
wgpu::StringView message) {
if (status != wgpu::RequestDeviceStatus::Success) {
std::cout << "RequestDevice: " << message << "\n";
exit(0);
}
device = std::move(d);
});
instance.WaitAny(f2, UINT64_MAX);
}
삼각형 그리기
브라우저에서 처리하므로 스왑 체인은 JavaScript API에 노출되지 않습니다. C++에서는 수동으로 만들어야 합니다. 편의를 위해 main.cpp
파일 상단에 wgpu::Surface
변수를 다시 선언합니다. Start()
에서 GLFW 창을 만든 직후 편리한 wgpu::glfw::CreateSurfaceForWindow()
함수를 호출하여 wgpu::Surface
(HTML 캔버스와 유사)를 만들고 InitGraphics()
에서 새 도우미 ConfigureSurface()
함수를 호출하여 구성합니다. 또한 while 루프에서 다음 텍스처를 표시하려면 surface.Present()
를 호출해야 합니다. 아직 렌더링이 발생하지 않으므로 눈에 띄는 효과는 없습니다.
#include <webgpu/webgpu_glfw.h>
…
wgpu::Surface surface;
wgpu::TextureFormat format;
void ConfigureSurface() {
wgpu::SurfaceCapabilities capabilities;
surface.GetCapabilities(adapter, &capabilities);
format = capabilities.formats[0];
wgpu::SurfaceConfiguration config{.device = device,
.format = format,
.width = kWidth,
.height = kHeight,
.presentMode = wgpu::PresentMode::Fifo};
surface.Configure(&config);
}
void InitGraphics() {
ConfigureSurface();
}
void Render() {
// TODO: Render a triangle using WebGPU.
}
void Start() {
…
surface = wgpu::glfw::CreateSurfaceForWindow(instance, window);
InitGraphics();
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
Render();
surface.Present();
instance.ProcessEvents();
}
}
이제 아래 코드를 사용하여 렌더링 파이프라인을 만들면 됩니다. 더 쉽게 액세스할 수 있도록 main.cpp
파일 상단에 wgpu::RenderPipeline
변수를 선언하고 InitGraphics()
에서 도우미 함수 CreateRenderPipeline()
를 호출합니다.
wgpu::RenderPipeline pipeline; … const char shaderCode[] = R"( @vertex fn vertexMain(@builtin(vertex_index) i : u32) -> @builtin(position) vec4f { const pos = array(vec2f(0, 1), vec2f(-1, -1), vec2f(1, -1)); return vec4f(pos[i], 0, 1); } @fragment fn fragmentMain() -> @location(0) vec4f { return vec4f(1, 0, 0, 1); } )"; void CreateRenderPipeline() { wgpu::ShaderSourceWGSL wgsl{{.code = shaderCode}}; wgpu::ShaderModuleDescriptor shaderModuleDescriptor{.nextInChain = &wgsl}; wgpu::ShaderModule shaderModule = device.CreateShaderModule(&shaderModuleDescriptor); wgpu::ColorTargetState colorTargetState{.format = format}; wgpu::FragmentState fragmentState{ .module = shaderModule, .targetCount = 1, .targets = &colorTargetState}; wgpu::RenderPipelineDescriptor descriptor{.vertex = {.module = shaderModule}, .fragment = &fragmentState}; pipeline = device.CreateRenderPipeline(&descriptor); } void InitGraphics() { … CreateRenderPipeline(); }
마지막으로 각 프레임에서 호출되는 Render()
함수에서 렌더링 명령어를 GPU에 전송합니다.
void Render() {
wgpu::SurfaceTexture surfaceTexture;
surface.GetCurrentTexture(&surfaceTexture);
wgpu::RenderPassColorAttachment attachment{
.view = surfaceTexture.texture.CreateView(),
.loadOp = wgpu::LoadOp::Clear,
.storeOp = wgpu::StoreOp::Store};
wgpu::RenderPassDescriptor renderpass{.colorAttachmentCount = 1,
.colorAttachments = &attachment};
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderpass);
pass.SetPipeline(pipeline);
pass.Draw(3);
pass.End();
wgpu::CommandBuffer commands = encoder.Finish();
device.GetQueue().Submit(1, &commands);
}
이제 CMake로 앱을 다시 빌드하고 실행하면 오랫동안 기다려 온 빨간색 삼각형이 창에 표시됩니다. 잠시 쉬세요.

WebAssembly로 컴파일
이제 기존 코드베이스를 조정하여 브라우저 창에 이 빨간색 삼각형을 그리는 데 필요한 최소한의 변경사항을 살펴보겠습니다. 여기서 앱은 JavaScript API 위에 webgpu.h를 구현하는 바인딩이 있는 emdawnwebgpu (Emscripten Dawn WebGPU)에 대해 빌드됩니다. C/C++ 프로그램을 WebAssembly로 컴파일하는 도구인 Emscripten을 사용합니다.
CMake 설정 업데이트
Emscripten이 설치되면 다음과 같이 CMakeLists.txt
빌드 파일을 업데이트합니다.
강조 표시된 코드만 변경하면 됩니다.
set_target_properties
는 타겟 파일에 'html' 파일 확장자를 자동으로 추가하는 데 사용됩니다. 즉, 'app.html' 파일을 생성합니다.emdawnwebgpu_cpp
타겟 링크 라이브러리는 Emscripten에서 WebGPU 지원을 사용 설정합니다. 이 권한이 없으면main.cpp
파일이webgpu/webgpu_cpp.h
파일에 액세스할 수 없습니다.ASYNCIFY=1
앱 링크 옵션을 사용하면 동기 C++ 코드가 비동기 JavaScript와 상호작용할 수 있습니다.USE_GLFW=3
앱 링크 옵션은 Emscripten에 GLFW 3 API의 기본 제공 JavaScript 구현을 사용하도록 지시합니다.
cmake_minimum_required(VERSION 3.22) # CMake version check
project(app) # Create project "app"
set(CMAKE_CXX_STANDARD 20) # Enable C++20 standard
add_executable(app "main.cpp")
set(DAWN_FETCH_DEPENDENCIES ON)
set(DAWN_BUILD_MONOLITHIC_LIBRARY STATIC)
add_subdirectory("dawn" EXCLUDE_FROM_ALL)
if(EMSCRIPTEN)
set_target_properties(app PROPERTIES SUFFIX ".html")
target_link_libraries(app PRIVATE emdawnwebgpu_cpp webgpu_glfw)
target_link_options(app PRIVATE "-sASYNCIFY=1" "-sUSE_GLFW=3")
else()
target_link_libraries(app PRIVATE webgpu_dawn webgpu_glfw glfw)
endif()
코드 업데이트
while 루프를 사용하는 대신 emscripten_set_main_loop(Render)
를 호출하여 Render()
함수가 브라우저 및 모니터와 적절하게 일치하는 적절한 부드러운 속도로 호출되도록 합니다.
#include <iostream>
#include <GLFW/glfw3.h>
#if defined(__EMSCRIPTEN__)
#include <emscripten/emscripten.h>
#endif
#include <dawn/webgpu_cpp_print.h>
#include <webgpu/webgpu_cpp.h>
#include <webgpu/webgpu_glfw.h>
void Start() {
…
#if defined(__EMSCRIPTEN__)
emscripten_set_main_loop(Render, 0, false);
#else
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
Render();
surface.Present();
instance.ProcessEvents();
}
#endif
}
Emscripten으로 앱 빌드
Emscripten으로 앱을 빌드하는 데 필요한 유일한 변경사항은 cmake
명령어 앞에 마법 같은 emcmake
셸 스크립트를 추가하는 것입니다. 이번에는 build-web
하위 폴더에서 앱을 생성하고 HTTP 서버를 시작합니다. 마지막으로 브라우저를 열고 build-web/app.html
로 이동합니다.
# Build the app with Emscripten.
$ emcmake cmake -B build-web && cmake --build build-web
# Start a HTTP server.
$ npx http-server

다음 단계
향후 기대할 수 있는 사항은 다음과 같습니다.
- webgpu.h 및 webgpu_cpp.h API 안정성 개선
- Android 및 iOS용 Dawn 초기 지원
그동안 제안사항과 질문이 포함된 Emscripten용 WebGPU 문제와 Dawn 문제를 신고해 주세요.
리소스
이 앱의 소스 코드를 자유롭게 살펴보세요.
WebGPU를 사용하여 C++로 처음부터 네이티브 3D 애플리케이션을 만드는 방법을 자세히 알아보려면 C++용 WebGPU 학습 문서 및 Dawn 네이티브 WebGPU 예시를 참고하세요.
Rust에 관심이 있다면 WebGPU 기반의 wgpu 그래픽 라이브러리를 살펴볼 수도 있습니다. hello-triangle 데모를 살펴보세요.
감사의 말
이 도움말은 코렌틴 왈레즈, 카이 니노미야, 레이첼 앤드류가 검토했습니다.
사진: 마르크올리비에 조두앙(Unsplash 제공)