diff --git a/.gitignore b/.gitignore index 029eb4e..99e35c5 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,5 @@ examples/raymarch/build/* docs/html source .DS_Store -third_party/lib/libdawn.* -third_party/lib/*.so -third_party/lib/*.dylib +third_party/lib/* +NUL \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 11a1417..6d4a743 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,7 @@ -cmake_minimum_required(VERSION 3.11) +cmake_minimum_required(VERSION 3.28) project(gpu) -include(FetchContent) -message(STATUS "CMAKE_CURRENT_SOURCE_DIR: ${CMAKE_CURRENT_SOURCE_DIR}") -set(FETCHCONTENT_BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party/fetchcontent") +include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/webgpu.cmake") set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # export compile_commands.json to use with LSP set(CMAKE_CXX_STANDARD 17) @@ -11,42 +9,11 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) option(USE_LOCAL_LIBS "Use local libraries instead of fetching from the internet" OFF) -# Define paths for local and remote repositories -set(WEBGPU_DIST_LOCAL_PATH "${CMAKE_CURRENT_SOURCE_DIR}/third_party/local/WebGPU-distribution") - -# Conditional assignment based on USE_LOCAL_LIBS -if(USE_LOCAL_LIBS) - set(WEBGPU_DIST_GIT_REPO ${WEBGPU_DIST_LOCAL_PATH}) - message(STATUS "Using local WebGPU distribution: ${WEBGPU_DIST_LOCAL_PATH}") -else() - set(WEBGPU_DIST_GIT_REPO "https://github.com/eliemichel/WebGPU-distribution") +# Ensure the build type is set +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build: Debug or Release" FORCE) endif() - -option(WEBGPU_TAG "WebGPU distribution tag to use") -IF (NOT WEBGPU_TAG) - set(WEBGPU_TAG "dawn") -ENDIF() -message(STATUS "Using WebGPU distribution tag: ${WEBGPU_TAG}") - -if (WEBGPU_TAG STREQUAL "dawn") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DWEBGPU_BACKEND_DAWN") - # use specific commit - # set(WEBGPU_TAG "1025b977e1927b6d0327e67352f90feb4bcf8274") - # set(WEBGPU_TAG "acf972b7b909f52e183bdae3971b93bb13d4a29e") - # add_compile_options(-UABSL_INTERNAL_AT_LEAST_CXX20) - # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -UABSL_INTERNAL_AT_LEAST_CXX20") - message(STATUS "Using Dawn backend") -endif() - -FetchContent_Declare( - webgpu - GIT_REPOSITORY ${WEBGPU_DIST_GIT_REPO} - GIT_TAG ${WEBGPU_TAG} - GIT_SHALLOW TRUE -) -FetchContent_MakeAvailable(webgpu) - option(FASTBUILD "Option to enable fast builds" OFF) if(FASTBUILD) set(CMAKE_BUILD_TYPE None) # Avoid default flags of predefined build types @@ -59,19 +26,18 @@ if(DEBUG) set(CMAKE_CXX_FLAGS "-O0 -g") endif() -# dl for dlopen/dlysm/dlclose -find_library(LIBDL dl REQUIRED) -if(LIBDL) - message(STATUS "Found libdl: ${LIBDL}") -else() - message(FATAL_ERROR "libdl not found") - exit() +if(WIN64) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DWEBGPU_BACKEND_DAWN") endif() +include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/gpu.cmake") -# Build the library target (libgpu) - -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) -set(SRC_LIB gpu.h utils/shaders.h utils/array_utils.h utilslogging.h) -add_library(gpu SHARED ${SRC_LIB}) -set_target_properties(gpu PROPERTIES LINKER_LANGUAGE CXX) +message(STATUS "CMAKE_CURRENT_SOURCE_DIR: ${CMAKE_CURRENT_SOURCE_DIR}") +message(STATUS "Include directories for wgpu: ${CMAKE_CURRENT_SOURCE_DIR}/third_party/headers") + +add_library(gpud SHARED gpu.h) +set_target_properties(gpud PROPERTIES LINKER_LANGUAGE CXX) +target_link_libraries(gpud PRIVATE wgpu) +target_link_libraries(gpud PRIVATE webgpu) +target_link_libraries(gpud PRIVATE gpu) +install(TARGETS gpud) \ No newline at end of file diff --git a/Makefile b/Makefile index b1138f5..0b40684 100644 --- a/Makefile +++ b/Makefile @@ -1,104 +1,195 @@ -NUM_JOBS=$(shell nproc) +NUM_JOBS=$(shell nproc 2>/dev/null || echo 1) CXX=clang++ -.PHONY: default examples/hello_world/build/hello_world tests libgpu debug build check-clang clean-build clean all watch-tests docs - -GPUCPP ?= $(PWD) -LIBDIR ?= $(GPUCPP)/third_party/lib -LIBSPEC ?= . $(GPUCPP)/source - -default: examples/hello_world/build/hello_world - -pch: - mkdir -p build && $(CXX) -std=c++17 -I$(GPUCPP) -I$(GPUCPP)/third_party/headers -x c++-header gpu.h -o build/gpu.h.pch - -# TODO(avh): change extension based on platform -lib: - mkdir -p build && $(CXX) -std=c++17 -I$(GPUCPP) -I$(GPUCPP)/third_party/headers -L$(LIBDIR) -ldawn -ldl -shared -fPIC gpu.cpp -o build/libgpucpp.dylib - -examples/hello_world/build/hello_world: check-clang dawnlib examples/hello_world/run.cpp check-linux-vulkan - $(LIBSPEC) && cd examples/hello_world && make build/hello_world && ./build/hello_world - -dawnlib: $(if $(wildcard third_party/lib/libdawn.so third_party/lib/libdawn.dylib),,run_setup) +.PHONY: default examples_hello_world_build_hello_world tests libgpu debug build check-entr check-clang clean-build clean clean-dawnlib clean-all all watch-tests docs +.PHONY: $(addprefix run_, $(TARGETS)) + +# List of targets (folders in your examples directory) +TARGETS := gpu_puzzles hello_world matmul physics render shadertui + +# Set up variables for cross-platform compatibility +ifeq ($(OS),Windows_NT) + DETECTED_OS := Windows + MKDIR_CMD := if not exist build mkdir build + RMDIR_CMD := rmdir + SLASH := \\ + LS_CMD := dir + LDLIB_SUFFIX := dll + EXPORT_CMD := set +else + DETECTED_OS := $(shell uname) + MKDIR_CMD := mkdir -p build + RMDIR_CMD := rm -rf + SLASH := / + LS_CMD := ls + LDLIB_SUFFIX := so + EXPORT_CMD := export +endif + +# Determine the architecture +ifeq ($(OS),Windows_NT) + ifeq ($(PROCESSOR_ARCHITECTURE),AMD64) + ARCH := x64 + else + ARCH := x86 + endif +else + ARCH := $(shell uname -m) + ifeq ($(ARCH), x86_64) + ARCH := x64 + else ifneq (,$(findstring arm, $(ARCH))) + ARCH := arm + endif +endif + +# Determine the build type +BUILD_TYPE ?= Release +LOWER_BUILD_TYPE ?= $(shell python3 -c "print('$(BUILD_TYPE)'.lower())") + +# Paths +GPUCPP ?= $(shell pwd) +LIBDIR ?= $(GPUCPP)$(SLASH)third_party$(SLASH)lib +LIBSPEC ?= . $(GPUCPP)$(SLASH)source + +default: run_hello_world + +# Define Run Rules +define RUN_RULES +run_$(1): + @cd examples && $(MAKE) run_$(1) +endef +# Apply Run Rules to each target in $(TARGETS) +$(foreach target,$(TARGETS),$(eval $(call RUN_RULES,$(target)))) + +# Build rules for specific targets +define BUILD_RULES +build_$(1): +ifeq ($(OS),Windows_NT) + cd examples && $(MAKE) $(1)_$(LOWER_BUILD_TYPE) +else + cd examples&& $(MAKE) $(1)_$(LOWER_BUILD_TYPE) +endif +endef +$(foreach target, $(TARGETS), $(eval $(call BUILD_RULES,$(target)))) + +# We use the custom "shell" based condition to check files cross-platform +dawnlib: +ifeq ($(OS),Windows_NT) + @if not exist "$(LIBDIR)$(SLASH)libdawn_$(ARCH)_$(BUILD_TYPE).dll" if not exist "$(LIBDIR)$(SLASH)libdawn.dll" $(MAKE) run_setup +else + @if [ ! -f "$(LIBDIR)$(SLASH)libdawn_$(ARCH)_$(BUILD_TYPE).so" ] && [ ! -f "$(LIBDIR)$(SLASH)libdawn.so" ] && [ ! -f "$(LIBDIR)$(SLASH)libdawn_$(ARCH)_$(BUILD_TYPE).dylib" ]; then \ + $(MAKE) run_setup; \ + fi +endif run_setup: check-python +ifeq ($(OS),Windows_NT) python3 setup.py +else + python3 >/dev/null 2>&1 && python3 setup.py +endif -all: dawnlib check-clang check-linux-vulkan lib pch - cd examples/gpu_puzzles && make build/gpu_puzzles - cd examples/hello_world && make build/hello_world - cd examples/matmul && make build/mm - cd examples/physics && make build/physics - cd examples/render && make build/render +all: dawnlib check-clang check-linux-vulkan + cd examples$(SLASH)gpu_puzzles && make build$(SLASH)gpu_puzzles + cd examples$(SLASH)hello_world && make build$(SLASH)hello_world + cd examples$(SLASH)matmul && make build$(SLASH)mm + cd examples$(SLASH)physics && make build$(SLASH)physics + cd examples$(SLASH)render && make build$(SLASH)render docs: Doxyfile doxygen Doxyfile -################################################################################ -# cmake targets (optional - precompiled binaries is preferred) -################################################################################ - -CMAKE_CMD = mkdir -p build && cd build && cmake .. -# Add --trace to see the cmake commands -FLAGS = -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON -DCMAKE_CXX_COMPILER=$(CXX) -DABSL_INTERNAL_AT_LEAST_CXX20=OFF -FASTBUILD_FLAGS = $(FLAGS) -DFASTBUILD:BOOL=ON -DEBUG_FLAGS = $(FLAGS) -DDEBUG:BOOL=ON -RELEASE_FLAGS = $(FLAGS) -DFASTBUILD:BOOL=OFF -TARGET_LIB=gpu - -libgpu-cmake: check-clang check-cmake - $(CMAKE_CMD) $(RELEASE_FLAGS) && make -j$(NUM_JOBS) gpu - -debug-cmake: check-clang check-cmake - $(CMAKE_CMD) $(DEBUG_FLAGS) && make -j$(NUM_JOBS) $(TARGET_ALL) - -all-cmake: check-clang check-cmake - $(CMAKE_CMD) $(RELEASE_FLAGS) && make -j$(NUM_JOBS) $(TARGET_ALL) - -################################################################################ # Cleanup ################################################################################ +# Clean rules for cleaning specific targets +define CLEAN_RULES +clean_$(1): +ifeq ($(OS),Windows_NT) + @if exist examples$(SLASH)$(1)$(SLASH)build ( $(RMDIR_CMD) /s examples$(SLASH)$(1)$(SLASH)build ) +else + find examples$(SLASH)$(1) -name build -type d | xargs rm -rf +endif +endef +$(foreach target,$(TARGETS),$(eval $(call CLEAN_RULES,$(target)))) + clean-dawnlib: - rm -f third_party/lib/libdawn.so third_party/lib/libdawn.dylib + $(RMDIR_CMD) $(LIBDIR)$(SLASH)libdawn*.* clean: - read -r -p "This will delete the contents of build/*. Are you sure? [CTRL-C to abort] " response && rm -rf build/* - rm -rf examples/gpu_puzzles/build/* - rm -rf examples/hello_world/build/* +ifeq ($(OS),Windows_NT) + @if exist build $(RMDIR_CMD) build /s /q + @if exist examples$(SLASH)gpu_puzzles$(SLASH)build $(RMDIR_CMD) examples$(SLASH)gpu_puzzles$(SLASH)build /s /q + @if exist examples$(SLASH)hello_world$(SLASH)build $(RMDIR_CMD) examples$(SLASH)hello_world$(SLASH)build /s /q + @if exist examples$(SLASH)matmul$(SLASH)build $(RMDIR_CMD) examples$(SLASH)matmul$(SLASH)build /s /q + @if exist examples$(SLASH)physics$(SLASH)build $(RMDIR_CMD) examples$(SLASH)physics$(SLASH)build /s /q + @if exist examples$(SLASH)render$(SLASH)build $(RMDIR_CMD) examples$(SLASH)render$(SLASH)build /s /q + @if exist build$(SLASH)gpu.h.pch del build$(SLASH)gpu.h.pch + $(MKDIR_CMD) +else + @command read -r -p "This will delete the contents of build/*. Are you sure? [CTRL-C to abort] " response && rm -rf build* + rm -rf examples/gpu_puzzles/build* + rm -rf examples/hello_world/build* rm -rf examples/matmul/build/mm rm -rf examples/physics/build/* rm -rf examples/render/build/* rm -f build/gpu.h.pch rm -f build/libgpucpp.so +endif clean-all: - read -r -p "This will delete the contents of build/* and third_party/*. Are you sure? [CTRL-C to abort] " response && rm -rf build/* third_party/fetchcontent/* third_party/gpu-build third_party/gpu-subbuild third_party/gpu-src third_party/lib/libdawn.so third_party/lib/libdawn.dylib +ifeq ($(OS),Windows_NT) + @if exist build $(RMDIR_CMD) build /s /q + $(RMDIR_CMD) third_party$(SLASH)fetchcontent /s /q + $(RMDIR_CMD) third_party$(SLASH)gpu-build /s /q + $(RMDIR_CMD) third_party$(SLASH)gpu-subbuild /s /q + $(RMDIR_CMD) third_party$(SLASH)gpu-src /s /q + $(RMDIR_CMD) third_party$(SLASH)lib /s /q + $(MKDIR_CMD) +else + read -r -p "This will delete the contents of build/* and third_party/*. Are you sure? [CTRL-C to abort] " response && rm -rf build* third_party/fetchcontent* third_party/gpu-build third_party/gpu-subbuild third_party/gpu-src third_party/lib/libdawn* third_party/lib/libdawn_$(ARCH)_$(BUILD_TYPE).* +endif -################################################################################ # Checks ################################################################################ -# check for the existence of clang++ and cmake +# check for the existence of clang++ check-clang: +ifeq ($(OS),Windows_NT) + @if not exist "$(shell where clang++.exe 2>NUL)" (echo "Please install clang++ with 'sudo apt-get install clang' or 'brew install llvm'" & exit 1) +else @command -v clang++ >/dev/null 2>&1 || { echo >&2 "Please install clang++ with 'sudo apt-get install clang' or 'brew install llvm'"; exit 1; } +endif + +# check for the existence of entr +check-entr: +ifeq ($(OS),Windows_NT) + @if not exist "$(shell where entr.exe 2>NUL)" (echo "Please install entr with 'brew install entr' or 'sudo apt-get install entr'" & exit 1) +else + @command -v entr >/dev/null 2>&1 || { echo >&2 "Please install entr with 'brew install entr' or 'sudo apt-get install entr'"; exit 1; } +endif +# check for the existence of cmake check-cmake: +ifeq ($(OS),Windows_NT) + @if not exist "$(shell where cmake.exe 2>NUL)" (echo "Please install cmake with 'sudo apt-get install cmake' or 'brew install cmake'" & exit 1) +else @command -v cmake >/dev/null 2>&1 || { echo >&2 "Please install cmake with 'sudo apt-get install cmake' or 'brew install cmake'"; exit 1; } +endif +# check for the existence of python3 check-python: - @command -v python3 >/dev/null 2>&1 || { echo >&2 "Python needs to be installed and in your path."; exit 1; } +ifeq ($(OS),Windows_NT) + @if not exist "$(shell where python3.exe 2>NUL)" (echo "Python needs to be installed and in your path." & exit 1) +else + @command -v python3 >/dev/null 2>&1 || { echo >&2 "Python needs to be installed and in your path."; exit 1; } +endif +# check the existence of Vulkan (Linux only) check-linux-vulkan: @echo "Checking system type and Vulkan availability..." - @if [ "$$(uname)" = "Linux" ]; then \ - if command -v vulkaninfo >/dev/null 2>&1; then \ - echo "Vulkan is installed."; \ - vulkaninfo; \ - else \ - echo "Vulkan is not installed. Please install Vulkan drivers to continue. On Debian / Ubuntu: sudo apt install libvulkan1 mesa-vulkan-drivers vulkan-tools"; \ - exit 1; \ - fi \ - else \ - echo "Non-Linux system detected. Skipping Vulkan check."; \ - fi +ifeq ($(OS),Linux) + @command -v vulkaninfo >/dev/null 2>&1 && { echo "Vulkan is installed."; vulkaninfo; } || { echo "Vulkan is not installed. Please install Vulkan drivers to continue. On Debian / Ubuntu: sudo apt install libvulkan1 mesa-vulkan-drivers vulkan-tools"; exit 1; } +else + @echo "Non-Linux system detected. Skipping Vulkan check."; +endif \ No newline at end of file diff --git a/README.md b/README.md index f77cddf..f22c1d2 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,10 @@ To build a gpu.cpp project, you will need to have installed on your system: make to build the project. - `make` to build the project. - Only on Linux systems - Vulkan drivers. If Vulkan is not installed, you can run `sudo apt install libvulkan1 mesa-vulkan-drivers vulkan-tools` to install them. +- On Windows, Vulkan, make, cmake, and a Visual Studio build environment with C++ tooling are required. +- WSL (Windows Subsystem for Linux) is also supported on Windows. You can install the required packages in WSL with `sudo apt install build-essential python3 libx11-xcb-dev libx11-dev libxi-dev libxcursor-dev libxinerama-dev libxrandr-dev libx11-6 libc++-dev libvulkan1 mesa-vulkan-drivers vulkan-tools clang`. -The only library dependency of gpu.cpp is a WebGPU implementation. Currently we support the Dawn native backend, but we plan to support other targets and WebGPU implementations (web browsers or other native implementations such as wgpu). Currently we support MacOS, Linux, and Windows (via WSL). +The only library dependency of gpu.cpp is a WebGPU implementation. Currently we support the Dawn native backend, but we plan to support other targets and WebGPU implementations (web browsers or other native implementations such as wgpu). Currently we support MacOS, Linux, and Windows, including WSL. Optionally, Dawn can be built from scratch with gpu.cpp using the cmake build scripts provided - see the -cmake targets in the Makefile. However, this is recommended for advanced users only. Building Dawn dependencies with cmake takes much longer than using the precompiled Dawn shared library. @@ -45,7 +47,7 @@ After cloning the repo, from the top-level gpu.cpp, you should be able to build make ``` -The first time you build and run the project this way, it will download a prebuilt shared library for the Dawn native WebGPU implementation automatically (using the setup.py script). This places the Dawn shared library in the third_party/lib directory. Afterwards you should see `libdawn.dylib` on MacOS or `libdawn.so` on Linux. This download only occurs once. +The first time you build and run the project this way, it will download a prebuilt shared library for the Dawn native WebGPU implementation automatically (using the setup.py script). This places the Dawn shared library in the third_party/lib directory. Afterwards you should see `libdawn_ARCH_BUILD_TYPE.dylib` on MacOS, `libdawn_ARCH_BUILD_TYPE.so` on Linux/WSL or `libdawn_ARCH_BUILD_TYPE.dll` on Windows. This download only occurs once. The build process itself should take a few seconds. If the build and executions is successful, you should see the output of the GELU computation: @@ -69,6 +71,19 @@ Hello gpu.cpp! Computed 10000 values of GELU(x) ``` +If you want to run other examples, you can run: + +``` +make run_example_name + +# For example: + +make run_matmul + +or + +make run_gpu_puzzles +``` If you need to clean up the build artifacts, you can run: @@ -76,6 +91,12 @@ If you need to clean up the build artifacts, you can run: make clean ``` +or for a specific example: + +``` +make clean_example_name +``` + ## Hello World Tutorial: A GELU Kernel As a real-world example for how to use gpu.cpp, let's start with a practical-but-simple example of a GPU kernel from neural networks. diff --git a/cmake/example.cmake b/cmake/example.cmake new file mode 100644 index 0000000..7779a30 --- /dev/null +++ b/cmake/example.cmake @@ -0,0 +1,56 @@ +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # export compile_commands.json to use with LSP +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +get_filename_component(PROJECT_ROOT ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) +get_filename_component(PROJECT_ROOT ${PROJECT_ROOT} DIRECTORY) + +# Construct potential paths +set(FILEPATH_CURRENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${FILENAME}") +set(FILEPATH_PROJECT_ROOT "${PROJECT_ROOT}/${FILENAME}") + +# Check if the file exists in the current directory +if(EXISTS ${FILEPATH_CURRENT_DIR}) + set(TARGET_FILE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) +elseif(EXISTS ${FILEPATH_PROJECT_ROOT}) + set(TARGET_FILE_PATH ${PROJECT_ROOT}) +else() + message(FATAL_ERROR "File ${FILENAME} not found in either ${CMAKE_CURRENT_SOURCE_DIR} or ${CMAKE_CURRENT_SOURCE_DIR}/../../") +endif() + +# Ensure the build type is set +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build: Debug or Release" FORCE) +endif() + +# Define architecture and build type directories or file names +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(ARCH "x64") +else() + set(ARCH "x86") +endif() + +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set(BUILD_TYPE "Debug") +else() + set(BUILD_TYPE "Release") +endif() + +if(NOT TARGET gpu) + message(STATUS "GPU_LIB not found") + include("${TARGET_FILE_PATH}/cmake/webgpu.cmake") + include("${TARGET_FILE_PATH}/cmake/gpu.cmake") +endif() + +add_executable(${PROJECT_NAME} run.cpp) +target_link_libraries(${PROJECT_NAME} PRIVATE gpu) +target_link_libraries(${PROJECT_NAME} PRIVATE wgpu) +target_link_libraries(${PROJECT_NAME} PRIVATE webgpu) + +if(WIN32) +# Ensure DLL is copied if on Windows +add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${DLL_PATH} + $) +endif() \ No newline at end of file diff --git a/cmake/gpu.cmake b/cmake/gpu.cmake new file mode 100644 index 0000000..e257cbf --- /dev/null +++ b/cmake/gpu.cmake @@ -0,0 +1,59 @@ +# Specify the filename to search for +set(FILENAME "gpu.h") + +get_filename_component(PROJECT_ROOT ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) +get_filename_component(PROJECT_ROOT ${PROJECT_ROOT} DIRECTORY) + +# Construct potential paths +set(FILEPATH_CURRENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${FILENAME}") +set(FILEPATH_PROJECT_ROOT "${PROJECT_ROOT}/${FILENAME}") + +# Check if the file exists in the current directory +if(EXISTS ${FILEPATH_CURRENT_DIR}) + set(TARGET_FILE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) +elseif(EXISTS ${FILEPATH_PROJECT_ROOT}) + set(TARGET_FILE_PATH ${PROJECT_ROOT}) +else() + message(FATAL_ERROR "File ${FILENAME} not found in either ${CMAKE_CURRENT_SOURCE_DIR} or ${CMAKE_CURRENT_SOURCE_DIR}/../../") +endif() + +# Define architecture and build type directories or file names +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(ARCH "x64") +else() + set(ARCH "x86") +endif() + +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set(BUILD_TYPE "Debug") +else() + set(BUILD_TYPE "Release") +endif() + +add_library(webgpulib SHARED IMPORTED) +add_library(gpu INTERFACE) +add_library(wgpu INTERFACE) +add_dependencies(gpu webgpulib) +# Define the header-only library +target_include_directories(gpu INTERFACE ${TARGET_FILE_PATH}) + +# Add headers webgpu.h +target_include_directories(wgpu INTERFACE ${TARGET_FILE_PATH}/third_party/headers) +if(WIN32) + set(DLL_PATH "${TARGET_FILE_PATH}/third_party/lib/libdawn_${ARCH}_${BUILD_TYPE}.dll") + if(EXISTS ${DLL_PATH}) + file(COPY ${DLL_PATH} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) + target_link_libraries(webgpulib INTERFACE ${DLL_PATH}) + else() + message(FATAL_ERROR "libdawn dll not found at: ${DLL_PATH}") +endif() +else() + find_library(LIBDAWN dawn REQUIRED PATHS "${TARGET_FILE_PATH}/third_party/lib") + if(LIBDAWN) + message(STATUS "Found libdawn: ${LIBDAWN}") + # Link against libdawn + target_link_libraries(webgpulib INTERFACE ${LIBDAWN}) + else() + message(FATAL_ERROR "libdawn not found") + endif() +endif() \ No newline at end of file diff --git a/cmake/webgpu.cmake b/cmake/webgpu.cmake new file mode 100644 index 0000000..5a66cfc --- /dev/null +++ b/cmake/webgpu.cmake @@ -0,0 +1,54 @@ +# Specify the filename to search for +set(FILENAME "gpu.h") + +get_filename_component(PROJECT_ROOT ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) +get_filename_component(PROJECT_ROOT ${PROJECT_ROOT} DIRECTORY) + +# Construct potential paths +set(FILEPATH_CURRENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${FILENAME}") +set(FILEPATH_PROJECT_ROOT "${PROJECT_ROOT}/${FILENAME}") + +# Check if the file exists in the current directory +if(EXISTS ${FILEPATH_CURRENT_DIR}) + set(TARGET_FILE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) +elseif(EXISTS ${FILEPATH_PROJECT_ROOT}) + set(TARGET_FILE_PATH ${PROJECT_ROOT}) +else() + message(FATAL_ERROR "File ${FILENAME} not found in either ${CMAKE_CURRENT_SOURCE_DIR} or ${CMAKE_CURRENT_SOURCE_DIR}/../../") +endif() + +include(FetchContent) + +set(FETCHCONTENT_BASE_DIR "${TARGET_FILE_PATH}/third_party/fetchcontent") +set(WEBGPU_DIST_LOCAL_PATH "${TARGET_FILE_PATH}/third_party/local/WebGPU-distribution") + +if(USE_LOCAL_LIBS) + set(WEBGPU_DIST_GIT_REPO ${WEBGPU_DIST_LOCAL_PATH}) + message(STATUS "Using local WebGPU distribution: ${WEBGPU_DIST_LOCAL_PATH}") +else() + set(WEBGPU_DIST_GIT_REPO "https://github.com/eliemichel/WebGPU-distribution") +endif() + +option(WEBGPU_TAG "WebGPU distribution tag to use") +if (NOT WEBGPU_TAG) + set(WEBGPU_TAG "dawn") +endif() +message(STATUS "Using WebGPU distribution tag: ${WEBGPU_TAG}") + +if (WEBGPU_TAG STREQUAL "dawn") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DWEBGPU_BACKEND_DAWN") + # use specific commit + # set(WEBGPU_TAG "1025b977e1927b6d0327e67352f90feb4bcf8274") + # set(WEBGPU_TAG "acf972b7b909f52e183bdae3971b93bb13d4a29e") + # add_compile_options(-UABSL_INTERNAL_AT_LEAST_CXX20) + # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -UABSL_INTERNAL_AT_LEAST_CXX20") + message(STATUS "Using Dawn backend") +endif() + +FetchContent_Declare( + webgpu + GIT_REPOSITORY ${WEBGPU_DIST_GIT_REPO} + GIT_TAG ${WEBGPU_TAG} + GIT_SHALLOW TRUE +) +FetchContent_MakeAvailable(webgpu) \ No newline at end of file diff --git a/examples/Makefile b/examples/Makefile new file mode 100644 index 0000000..8308ac7 --- /dev/null +++ b/examples/Makefile @@ -0,0 +1,144 @@ +# List of targets (folders in your examples directory) +TARGETS := gpu_puzzles hello_world matmul physics render shadertui + +# Set up variables for cross-platform compatibility +ifeq ($(OS),Windows_NT) + DETECTED_OS := Windows + MKDIR_CMD := if not exist build mkdir build + RMDIR_CMD := rd /s /q + SLASH := \\ + LDLIB_SUFFIX := dll + EXPORT_CMD := set +else + DETECTED_OS := $(shell uname) + MKDIR_CMD := mkdir -p build + RMDIR_CMD := rm -rf + SLASH := / + LDLIB_SUFFIX := so + EXPORT_CMD := export +endif + +# Determine the architecture +ifeq ($(OS),Windows_NT) + ifeq ($(PROCESSOR_ARCHITECTURE),AMD64) + ARCH := x64 + else + ARCH := x86 + endif +else + ARCH := $(shell uname -m) + ifeq ($(ARCH), x86_64) + ARCH := x64 + else ifneq (,$(findstring arm, $(ARCH))) + ARCH := arm + endif +endif + +# Paths +GPUCPP ?= $(shell pwd) + +.PHONY: default all_release all_debug dawnlib run_setup check-python +.PHONY: $(addsuffix _release, $(TARGETS)) +.PHONY: $(addsuffix _debug, $(TARGETS)) +.PHONY: clean-all $(addprefix clean_, $(TARGETS)) + +default: all_debug + +all_release: $(addsuffix _release, $(TARGETS)) + +all_debug: $(addsuffix _debug, $(TARGETS)) + +# Define Build Rules +define BUILD_RULES +$(1)_release: +ifeq ($(OS),Windows_NT) + @if not exist "$(1)$(SLASH)build" $(MKDIR_CMD) "$(1)$(SLASH)build" + cd $(1) && \ + cmake -B build -DCMAKE_BUILD_TYPE=Release && \ + cmake --build build --config Release +else + $(MKDIR_CMD) $(1)$(SLASH)build + cd $(1) && \ + cmake -B build -DCMAKE_BUILD_TYPE=Release && \ + cmake --build build --config Release +endif + +$(1)_debug: +ifeq ($(OS),Windows_NT) + @if not exist "$(1)$(SLASH)build" $(MKDIR_CMD) "$(1)$(SLASH)build" + cd $(1) && \ + cmake -B build -DCMAKE_BUILD_TYPE=Debug && \ + cmake --build build --config Debug +else + $(MKDIR_CMD) $(1)$(SLASH)build + cd $(1) && \ + cmake -B build -DCMAKE_BUILD_TYPE=Debug && \ + cmake --build build --config Debug +endif +endef +# Apply Build Rules to each target in $(TARGETS) +$(foreach target,$(TARGETS),$(eval $(call BUILD_RULES,$(target)))) + +# Define Run Rules with Build if Executable Not Found +define RUN_RULES +run_$(1): +ifeq ($(OS),Windows_NT) + @if exist $(1)$(SLASH)build$(SLASH)Release$(SLASH)$(1).exe ( \ + echo "Running $(1)" && \ + $(1)$(SLASH)build$(SLASH)Release$(SLASH)$(1).exe \ + ) else ( \ + echo "Executable not found; building $(1) first" && \ + $(MAKE) $(1)_release && \ + $(1)$(SLASH)build$(SLASH)Release$(SLASH)$(1).exe \ + ) +else + @if [ -x "$(shell find $(1)/build -type f -executable -name $(1) | head -n 1)" ]; then \ + exe_path=$(shell find $(1)/build -type f -executable -name $(1) | head -n 1) && \ + echo "Running $(1)" && \ + $$exe_path; \ + else \ + echo "Executable not found; building $(1) first" && \ + $(MAKE) $(1)_release && \ + exe_path=$(shell find $(1)/build -type f -executable -name $(1) | head -n 1) && \ + $$exe_path; \ + fi +endif +endef +# Apply Run Rules to each target in $(TARGETS) +$(foreach target,$(TARGETS),$(eval $(call RUN_RULES,$(target)))) + +# Clean rules for cleaning specific targets +define CLEAN_RULES +clean_$(1): +ifeq ($(OS),Windows_NT) + if exist $(1)$(SLASH)build ( $(RMDIR_CMD) $(1)$(SLASH)build ) +else + find $(1) -name build -type d | xargs rm -rf +endif +endef +$(foreach target,$(TARGETS),$(eval $(call CLEAN_RULES,$(target)))) + +clean-all: $(addprefix clean_, $(TARGETS)) + +dawnlib: +ifeq ($(OS),Windows_NT) + @if not exist "$(GPUCPP)$(SLASH)third_party$(SLASH)lib$(SLASH)libdawn_$(ARCH)_$(BUILD_TYPE).dll" if not exist "$(GPUCPP)$(SLASH)third_party$(SLASH)lib$(SLASH)libdawn.dll" (make run_setup) +else + @if [ ! -f "$(GPUCPP)/third_party/lib/libdawn_$(ARCH)_$(BUILD_TYPE).so" ] && [ ! -f "$(GPUCPP)/third_party/lib/libdawn.so" ] && [ ! -f "$(GPUCPP)/third_party/lib/libdawn_$(ARCH)_$(BUILD_TYPE).dylib" ]; then \ + $(MAKE) run_setup; \ + fi +endif + +run_setup: check-python +ifeq ($(OS),Windows_NT) + cd $(GPUCPP) && if exist "python3.exe" (python3 setup.py) else (if exist "python.exe" (python setup.py) else (echo "Python needs to be installed and in your path." & exit 1)) +else + cd $(GPUCPP) && (command -v python3 >/dev/null 2>&1 && python3 setup.py || python setup.py) +endif + +check-python: +ifeq ($(OS),Windows_NT) + @if not exist "$(shell where python3.exe 2>NUL)" if not exist "$(shell where python.exe 2>NUL)" (echo "Python needs to be installed and in your path." & exit 1) +else + @command -v python3 >/dev/null 2>&1 || command -v python >/dev/null 2>&1 || { echo "Python needs to be installed and in your path."; exit 1; } +endif \ No newline at end of file diff --git a/examples/gpu_puzzles/CMakeLists.txt b/examples/gpu_puzzles/CMakeLists.txt index 331d695..1ca57d2 100644 --- a/examples/gpu_puzzles/CMakeLists.txt +++ b/examples/gpu_puzzles/CMakeLists.txt @@ -1,29 +1,22 @@ -cmake_minimum_required(VERSION 3.11) +cmake_minimum_required(VERSION 3.28) project(gpu_puzzles) -include(FetchContent) -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(FILENAME "gpu.h") -message(STATUS "CMAKE_CURRENT_SOURCE_DIR: " ${CMAKE_CURRENT_SOURCE_DIR}) -message(STATUS "LIBRARY DIRECTORY: " ${CMAKE_CURRENT_SOURCE_DIR}/../../) +get_filename_component(PROJECT_ROOT ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) +get_filename_component(PROJECT_ROOT ${PROJECT_ROOT} DIRECTORY) -# For a standalone repo, remove this line and set the path to the repos own -# FetchContent cache directory. Alternatively, don't set FETCHCONTENT_BASE_DIR -# and the repos will be downloaded to the build directory. -set(FETCHCONTENT_BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../third_party") +# Construct potential paths +set(FILEPATH_CURRENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${FILENAME}") +set(FILEPATH_PROJECT_ROOT "${PROJECT_ROOT}/${FILENAME}") -FetchContent_Declare( - gpu - # For standalone repo, replace GIT_REPOSITORY with the URL: - # GIT_REPOSITORY https://github.com/AnswerDotAI/gpu.cpp - GIT_REPOSITORY file://${CMAKE_CURRENT_SOURCE_DIR}/../../ - GIT_TAG main - GIT_SHALLOW TRUE -) -FetchContent_MakeAvailable(gpu) - -add_executable(gpu_puzzles run.cpp) -target_link_libraries(gpu_puzzles gpu webgpu) -target_include_directories(gpu_puzzles PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../ ) +# Check if the file exists in the current directory +if(EXISTS ${FILEPATH_CURRENT_DIR}) + set(TARGET_FILE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) +elseif(EXISTS ${FILEPATH_PROJECT_ROOT}) + set(TARGET_FILE_PATH ${PROJECT_ROOT}) +else() + message(FATAL_ERROR "File ${FILENAME} not found in either ${CMAKE_CURRENT_SOURCE_DIR} or ${CMAKE_CURRENT_SOURCE_DIR}/../../") +endif() +include("${TARGET_FILE_PATH}/cmake/example.cmake") \ No newline at end of file diff --git a/examples/gpu_puzzles/Makefile b/examples/gpu_puzzles/Makefile deleted file mode 100644 index 849240c..0000000 --- a/examples/gpu_puzzles/Makefile +++ /dev/null @@ -1,32 +0,0 @@ -CXX=clang++ -GPUCPP ?= $(PWD)/../.. -LIBDIR ?= $(GPUCPP)/third_party/lib -LIBSPEC ?= . $(GPUCPP)/source -NUM_JOBS?=$(shell nproc) -TARGET=gpu_puzzles -ifeq ($(shell $(CXX) -std=c++17 -x c++ -E -include array - < /dev/null > /dev/null 2>&1 ; echo $$?),0) - STDLIB := -else - STDLIB := -stdlib=libc++ -endif -FLAGS=-std=c++17 $(STDLIB) -I$(GPUCPP) -I$(GPUCPP)/third_party/headers -L$(GPUCPP)/third_party/lib run.cpp -ldl -ldawn -FLAGS_KEY=-std=c++17 $(STDLIB) -I$(GPUCPP) -I$(GPUCPP)/third_party/headers -L$(GPUCPP)/third_party/lib key.cpp -ldl -ldawn - -run: ./build/$(TARGET) - $(LIBSPEC) && ./build/$(TARGET) - -run-key: ./build/key - $(LIBSPEC) && ./build/key - -build/$(TARGET): run.cpp - mkdir -p build && $(CXX) $(FLAGS) -o ./build/$(TARGET) - -build/key: key.cpp - mkdir -p build && $(CXX) $(FLAGS_KEY) -o ./build/key - -watch: - @command -v entr >/dev/null 2>&1 || { echo >&2 "Please install entr with 'brew install entr' or 'sudo apt-get install entr'"; exit 1; } - mkdir -p build && ls | entr -s "rm -f ./build/$(TARGET) && make -j$(NUM_JOBS) ./build/$(TARGET) && $(LIBSPEC) && ./build/$(TARGET)" - -clean: - read -r -p "This will delete the contents of build/*. Are you sure? [CTRL-C to abort] " response && rm -rf build/* diff --git a/examples/hello_world/CMakeLists.txt b/examples/hello_world/CMakeLists.txt new file mode 100644 index 0000000..9feebc9 --- /dev/null +++ b/examples/hello_world/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.28) +project(hello_world) + +set(FILENAME "gpu.h") + +get_filename_component(PROJECT_ROOT ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) +get_filename_component(PROJECT_ROOT ${PROJECT_ROOT} DIRECTORY) + +# Construct potential paths +set(FILEPATH_CURRENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${FILENAME}") +set(FILEPATH_PROJECT_ROOT "${PROJECT_ROOT}/${FILENAME}") + +# Check if the file exists in the current directory +if(EXISTS ${FILEPATH_CURRENT_DIR}) + set(TARGET_FILE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) +elseif(EXISTS ${FILEPATH_PROJECT_ROOT}) + set(TARGET_FILE_PATH ${PROJECT_ROOT}) +else() + message(FATAL_ERROR "File ${FILENAME} not found in either ${CMAKE_CURRENT_SOURCE_DIR} or ${CMAKE_CURRENT_SOURCE_DIR}/../../") +endif() + +include("${TARGET_FILE_PATH}/cmake/example.cmake") \ No newline at end of file diff --git a/examples/hello_world/Makefile b/examples/hello_world/Makefile deleted file mode 100644 index 065e4ff..0000000 --- a/examples/hello_world/Makefile +++ /dev/null @@ -1,36 +0,0 @@ -CXX=clang++ -GPUCPP ?= $(PWD)/../.. -LIBDIR ?= $(GPUCPP)/third_party/lib -LIBSPEC ?= . $(GPUCPP)/source -NUM_JOBS?=$(shell nproc) -TARGET=hello_world -ifeq ($(shell $(CXX) -std=c++17 -x c++ -E -include array - < /dev/null > /dev/null 2>&1 ; echo $$?),0) - STDLIB := -else - STDLIB := -stdlib=libc++ -endif -FLAGS=-std=c++17 $(STDLIB) -I$(GPUCPP) -I$(GPUCPP)/third_party/headers -L$(GPUCPP)/third_party/lib run.cpp -ldl -ldawn - -run: ./build/$(TARGET) dawnlib - $(LIBSPEC) && ./build/$(TARGET) - -dawnlib: $(if $(wildcard $(GPUCPP)/third_party/lib/libdawn.so $(GPUCPP)/third_party/lib/libdawn.dylib),,run_setup) - -run_setup: check-python - cd $(GPUCPP) && python3 setup.py - -all: dawnlib check-clang check-linux-vulkan - cd examples/gpu_puzzles && make build/gpu_puzzles - cd examples/hello_world && make build/hello_world - cd examples/matmul && make build/mm - cd examples/physics && make build/physics - cd examples/render && make build/render - -build/$(TARGET): run.cpp - mkdir -p build && $(CXX) $(FLAGS) -DNDEBUG -o ./build/$(TARGET) - -clean: - read -r -p "This will delete the contents of build/*. Are you sure? [CTRL-C to abort] " response && rm -rf build/* - -check-python: - @command -v python3 >/dev/null 2>&1 || { echo >&2 "Python needs to be installed and in your path."; exit 1; } diff --git a/examples/matmul/CMakeLists.txt b/examples/matmul/CMakeLists.txt new file mode 100644 index 0000000..5b4f3f9 --- /dev/null +++ b/examples/matmul/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.28) +project(matmul) + +set(FILENAME "gpu.h") + +get_filename_component(PROJECT_ROOT ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) +get_filename_component(PROJECT_ROOT ${PROJECT_ROOT} DIRECTORY) + +# Construct potential paths +set(FILEPATH_CURRENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${FILENAME}") +set(FILEPATH_PROJECT_ROOT "${PROJECT_ROOT}/${FILENAME}") + +# Check if the file exists in the current directory +if(EXISTS ${FILEPATH_CURRENT_DIR}) + set(TARGET_FILE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) +elseif(EXISTS ${FILEPATH_PROJECT_ROOT}) + set(TARGET_FILE_PATH ${PROJECT_ROOT}) +else() + message(FATAL_ERROR "File ${FILENAME} not found in either ${CMAKE_CURRENT_SOURCE_DIR} or ${CMAKE_CURRENT_SOURCE_DIR}/../../") +endif() + +include("${TARGET_FILE_PATH}/cmake/example.cmake") \ No newline at end of file diff --git a/examples/matmul/Makefile b/examples/matmul/Makefile deleted file mode 100644 index e1eab06..0000000 --- a/examples/matmul/Makefile +++ /dev/null @@ -1,27 +0,0 @@ -CXX=clang++ -GPUCPP ?= $(PWD)/../.. -LIBDIR ?= $(GPUCPP)/third_party/lib -LIBSPEC ?= . $(GPUCPP)/source -NUM_JOBS?=$(shell nproc) -CODEPATH = find . ../../utils ../../ -maxdepth 1 -type f -TARGET=mm -ifeq ($(shell $(CXX) -std=c++17 -x c++ -E -include array - < /dev/null > /dev/null 2>&1 ; echo $$?),0) - STDLIB := -else - STDLIB := -stdlib=libc++ -endif -FLAGS=-std=c++17 $(STDLIB) -I$(GPUCPP) -I$(GPUCPP)/third_party/headers -L$(GPUCPP)/third_party/lib run.cpp -ldl -ldawn - -run: ./build/$(TARGET) - $(LIBSPEC) && ./build/$(TARGET) - -# Use clang -v to see the include paths -build/$(TARGET): run.cpp - mkdir -p build && $(CXX) $(FLAGS) -o ./build/$(TARGET) - -watch: - @command -v entr >/dev/null 2>&1 || { echo >&2 "Please install entr with 'brew install entr' or 'sudo apt-get install entr'"; exit 1; } - mkdir -p build && $(CODEPATH) | entr -s "$(LIBSPEC) && rm -f ./build/$(TARGET) && make -j$(NUM_JOBS) ./build/$(TARGET) && ./build/$(TARGET)" - -clean: - read -r -p "This will delete the contents of build/*. Are you sure? [CTRL-C to abort] " response && rm -rf build/* diff --git a/examples/matmul/run.cpp b/examples/matmul/run.cpp index 4e61968..2e0cbc6 100644 --- a/examples/matmul/run.cpp +++ b/examples/matmul/run.cpp @@ -7,6 +7,10 @@ #include "gpu.h" // createContext, createTensor, createKernel, dispatchKernel, // wait, resetCommandBuffer, toCPU +#ifndef M_PI + #define M_PI 3.14159265358979323846 +#endif + #include "llmc/reference_impls.h" // for CPU reference implementation #include "utils/array_utils.h" // show, isclose, randn, randint #include "utils/logging.h" // LOG diff --git a/examples/physics/CMakeLists.txt b/examples/physics/CMakeLists.txt new file mode 100644 index 0000000..69e9925 --- /dev/null +++ b/examples/physics/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.28) +project(physics) + +set(FILENAME "gpu.h") + +get_filename_component(PROJECT_ROOT ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) +get_filename_component(PROJECT_ROOT ${PROJECT_ROOT} DIRECTORY) + +# Construct potential paths +set(FILEPATH_CURRENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${FILENAME}") +set(FILEPATH_PROJECT_ROOT "${PROJECT_ROOT}/${FILENAME}") + +# Check if the file exists in the current directory +if(EXISTS ${FILEPATH_CURRENT_DIR}) + set(TARGET_FILE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) +elseif(EXISTS ${FILEPATH_PROJECT_ROOT}) + set(TARGET_FILE_PATH ${PROJECT_ROOT}) +else() + message(FATAL_ERROR "File ${FILENAME} not found in either ${CMAKE_CURRENT_SOURCE_DIR} or ${CMAKE_CURRENT_SOURCE_DIR}/../../") +endif() + +include("${TARGET_FILE_PATH}/cmake/example.cmake") \ No newline at end of file diff --git a/examples/physics/Makefile b/examples/physics/Makefile deleted file mode 100644 index 7cdd3f5..0000000 --- a/examples/physics/Makefile +++ /dev/null @@ -1,24 +0,0 @@ -CXX=clang++ -GPUCPP ?= $(PWD)/../.. -LIBDIR ?= $(GPUCPP)/third_party/lib -LIBSPEC ?= . $(GPUCPP)/source -NUM_JOBS?=$(shell nproc) -TARGET=physics -ifeq ($(shell $(CXX) -std=c++17 -x c++ -E -include array - < /dev/null > /dev/null 2>&1 ; echo $$?),0) - STDLIB := -else - STDLIB := -stdlib=libc++ -endif -FLAGS=-std=c++17 $(STDLIB) -I$(GPUCPP) -I$(GPUCPP)/third_party/headers -L$(GPUCPP)/third_party/lib run.cpp -ldl -ldawn - -run: ./build/$(TARGET) - $(LIBSPEC) && ./build/$(TARGET) - -build/$(TARGET): run.cpp - mkdir -p build && $(CXX) $(FLAGS) -o ./build/$(TARGET) - -watch: - mkdir -p build && ls | entr -s "rm -f ./build/$(TARGET) && make -j$(NUM_JOBS) ./build/$(TARGET) && $(LIBSPEC) && ./build/$(TARGET)" - -clean: - read -r -p "This will delete the contents of build/*. Are you sure? [CTRL-C to abort] " response && rm -rf build/* diff --git a/examples/render/CMakeLists.txt b/examples/render/CMakeLists.txt new file mode 100644 index 0000000..f3599cf --- /dev/null +++ b/examples/render/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.28) +project(render) + +set(FILENAME "gpu.h") + +get_filename_component(PROJECT_ROOT ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) +get_filename_component(PROJECT_ROOT ${PROJECT_ROOT} DIRECTORY) + +# Construct potential paths +set(FILEPATH_CURRENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${FILENAME}") +set(FILEPATH_PROJECT_ROOT "${PROJECT_ROOT}/${FILENAME}") + +# Check if the file exists in the current directory +if(EXISTS ${FILEPATH_CURRENT_DIR}) + set(TARGET_FILE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) +elseif(EXISTS ${FILEPATH_PROJECT_ROOT}) + set(TARGET_FILE_PATH ${PROJECT_ROOT}) +else() + message(FATAL_ERROR "File ${FILENAME} not found in either ${CMAKE_CURRENT_SOURCE_DIR} or ${CMAKE_CURRENT_SOURCE_DIR}/../../") +endif() + +include("${TARGET_FILE_PATH}/cmake/example.cmake") \ No newline at end of file diff --git a/examples/render/Makefile b/examples/render/Makefile deleted file mode 100644 index 552bbf0..0000000 --- a/examples/render/Makefile +++ /dev/null @@ -1,25 +0,0 @@ -CXX=clang++ -GPUCPP ?= $(PWD)/../.. -LIBDIR ?= $(GPUCPP)/third_party/lib -LIBSPEC ?= . $(GPUCPP)/source -NUM_JOBS?=$(shell nproc) -TARGET=render -ifeq ($(shell $(CXX) -std=c++17 -x c++ -E -include array - < /dev/null > /dev/null 2>&1 ; echo $$?),0) - STDLIB := -else - STDLIB := -stdlib=libc++ -endif -FLAGS=-std=c++17 $(STDLIB) -I$(GPUCPP) -I$(GPUCPP)/third_party/headers -L$(GPUCPP)/third_party/lib run.cpp -ldl -ldawn - -run: ./build/$(TARGET) - $(LIBSPEC) && ./build/$(TARGET) - -build/$(TARGET): run.cpp - mkdir -p build && $(CXX) $(FLAGS) -o ./build/$(TARGET) - -watch: - @command -v entr >/dev/null 2>&1 || { echo >&2 "Please install entr with 'brew install entr' or 'sudo apt-get install entr'"; exit 1; } - mkdir -p build && ls | entr -s "rm -f ./build/$(TARGET) && make -j$(NUM_JOBS) ./build/$(TARGET) && $(LIBSPEC) && ./build/$(TARGET)" - -clean: - read -r -p "This will delete the contents of build/*. Are you sure? [CTRL-C to abort] " response && rm -rf build/* diff --git a/examples/render/run.cpp b/examples/render/run.cpp index fce708f..8d9e4fb 100644 --- a/examples/render/run.cpp +++ b/examples/render/run.cpp @@ -80,21 +80,24 @@ fn main(@builtin(global_invocation_id) GlobalInvocationID: vec3) { } )"; -std::uint32_t getCurrentTimeInMilliseconds() { +std::uint32_t getCurrentTimeInMilliseconds() +{ auto now = std::chrono::system_clock::now(); auto duration = std::chrono::duration_cast( now.time_since_epoch()); return static_cast(duration.count()); } -int main(int argc, char **argv) { +int main(int argc, char **argv) +{ constexpr size_t NROWS = 32; constexpr size_t NCOLS = 64; std::array screen; - struct Params { + struct Params + { float focalLength; uint32_t screenWidth; uint32_t screenHeight; @@ -143,37 +146,42 @@ int main(int argc, char **argv) { float min = 0.0; float max = params.sphereRadius * 3; - for (size_t i = 0; i < screen.size(); ++i) { + for (size_t i = 0; i < screen.size(); ++i) + { screen[i] = (screen[i] - min) / (max - min); } std::array raster; - for (size_t i = 0; i < screen.size(); ++i) { + for (size_t i = 0; i < screen.size(); ++i) + { size_t index = std::min(sizeof(intensity) - 2, - std::max(0ul, static_cast(screen[i] * - (sizeof(intensity) - 2)))); + std::max(size_t{0}, static_cast(screen[i] * (sizeof(intensity) - 2)))); raster[i] = intensity[index]; } char buffer[(NROWS + 2) * (NCOLS + 2)]; char *offset = buffer; sprintf(offset, "+"); - for (size_t col = 0; col < NCOLS; ++col) { + for (size_t col = 0; col < NCOLS; ++col) + { sprintf(offset + col + 1, "-"); } sprintf(buffer + NCOLS + 1, "+\n"); offset += NCOLS + 3; - for (size_t row = 0; row < NROWS; ++row) { + for (size_t row = 0; row < NROWS; ++row) + { sprintf(offset, "|"); - for (size_t col = 0; col < NCOLS; ++col) { + for (size_t col = 0; col < NCOLS; ++col) + { sprintf(offset + col + 1, "%c", raster[row * NCOLS + col]); } sprintf(offset + NCOLS + 1, "|\n"); offset += NCOLS + 3; } sprintf(offset, "+"); - for (size_t col = 0; col < NCOLS; ++col) { + for (size_t col = 0; col < NCOLS; ++col) + { sprintf(offset + col + 1, "-"); } sprintf(offset + NCOLS + 1, "+\n"); diff --git a/examples/shadertui/CMakeLists.txt b/examples/shadertui/CMakeLists.txt new file mode 100644 index 0000000..083b718 --- /dev/null +++ b/examples/shadertui/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.28) +project(shadertui) + +set(FILENAME "gpu.h") + +get_filename_component(PROJECT_ROOT ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) +get_filename_component(PROJECT_ROOT ${PROJECT_ROOT} DIRECTORY) + +# Construct potential paths +set(FILEPATH_CURRENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${FILENAME}") +set(FILEPATH_PROJECT_ROOT "${PROJECT_ROOT}/${FILENAME}") + +# Check if the file exists in the current directory +if(EXISTS ${FILEPATH_CURRENT_DIR}) + set(TARGET_FILE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) +elseif(EXISTS ${FILEPATH_PROJECT_ROOT}) + set(TARGET_FILE_PATH ${PROJECT_ROOT}) +else() + message(FATAL_ERROR "File ${FILENAME} not found in either ${CMAKE_CURRENT_SOURCE_DIR} or ${CMAKE_CURRENT_SOURCE_DIR}/../../") +endif() + +include("${TARGET_FILE_PATH}/cmake/example.cmake") \ No newline at end of file diff --git a/examples/shadertui/Makefile b/examples/shadertui/Makefile deleted file mode 100644 index 49049f3..0000000 --- a/examples/shadertui/Makefile +++ /dev/null @@ -1,28 +0,0 @@ -CXX=clang++ -GPUCPP ?= $(PWD)/../.. -LIBDIR ?= $(GPUCPP)/third_party/lib -LIBSPEC ?= . $(GPUCPP)/source -NUM_JOBS?=$(shell nproc) -TARGET=shadertui -# FLAGS=-stdlib=libc++ -std=c++17 -I$(GPUCPP) -I$(GPUCPP)/third_party/headers -L$(GPUCPP)/third_party/lib run.cpp -ldl -ldawn -CODEPATH = find . ../../utils ../../ -maxdepth 1 -type f -ifeq ($(shell $(CXX) -std=c++17 -x c++ -E -include array - < /dev/null > /dev/null 2>&1 ; echo $$?),0) - STDLIB := -else - STDLIB := -stdlib=libc++ -endif -FLAGS=-std=c++17 $(STDLIB) -I$(GPUCPP) -I$(GPUCPP)/third_party/headers -L$(GPUCPP)/third_party/lib run.cpp -ldl -ldawn - -run: ./build/$(TARGET) - $(LIBSPEC) && ./build/$(TARGET) - -# Use clang -v to see the include paths -build/$(TARGET): run.cpp - mkdir -p build && $(CXX) $(FLAGS) -o ./build/$(TARGET) - -watch: - @command -v entr >/dev/null 2>&1 || { echo >&2 "Please install entr with 'brew install entr' or 'sudo apt-get install entr'"; exit 1; } - mkdir -p build && $(CODEPATH) | entr -s "$(LIBSPEC) && rm -f ./build/$(TARGET) && make -j$(NUM_JOBS) run" - -clean: - read -r -p "This will delete the contents of build/*. Are you sure? [CTRL-C to abort] " response && rm -rf build/* diff --git a/examples/shadertui/run.cpp b/examples/shadertui/run.cpp index 669dc30..fda19e1 100644 --- a/examples/shadertui/run.cpp +++ b/examples/shadertui/run.cpp @@ -14,19 +14,22 @@ using namespace gpu; template void rasterize(const std::array &values, - std::array &raster) { - // Note: We can experiment with the rasterization characters here but fewer - // characters looks better by imposing temporal coherence whereas more - // characters can start to look like noise. + std::array &raster) +{ + // Can experiment with the rasterization characters here but fewer characters + // looks better by imposing temporal coherence whereas more characters can + // start to look like noise. // static const char intensity[] = " `.-':_,^=;><+!ngrc*/z?sLTv)J7(|Fi{C}fI31tlu[neoZ5Yxjya]2ESwqkP6h9d4VpOGbUAKXHm8RD#$Bg0MNWQ%&@"; static const char intensity[] = " .`'^-+=*x17X$8#%@"; - for (size_t i = 0; i < rows; ++i) { - for (size_t j = 0; j < cols; ++j) { + for (size_t i = 0; i < rows; ++i) + { + for (size_t j = 0; j < cols; ++j) + { // values ranges b/w 0 and 1 - size_t index = - std::min(sizeof(intensity) - 2, - std::max(0ul, static_cast(values[i * cols + j] * - (sizeof(intensity) - 2)))); + size_t index = std::min(sizeof(intensity) - 2, + std::max(size_t{0}, + static_cast(values[i * cols + j] * + (sizeof(intensity) - 2)))); raster[i * (cols + 1) + j] = intensity[index]; } raster[i * (cols + 1) + cols] = '\n'; @@ -34,7 +37,8 @@ void rasterize(const std::array &values, } float getCurrentTimeInMilliseconds( - std::chrono::time_point &zeroTime) { + std::chrono::time_point &zeroTime) +{ std::chrono::duration duration = std::chrono::high_resolution_clock::now() - zeroTime; return duration.count(); @@ -43,19 +47,22 @@ float getCurrentTimeInMilliseconds( void loadKernelCode(const std::string &filename, std::string &codeString) { codeString = ""; FILE *file = fopen(filename.c_str(), "r"); - while (!file) { + while (!file) + { fclose(file); std::this_thread::sleep_for(std::chrono::milliseconds(10)); file = fopen(filename.c_str(), "r"); } char buffer[4096]; - while (fgets(buffer, sizeof(buffer), file)) { + while (fgets(buffer, sizeof(buffer), file)) + { codeString += buffer; } fclose(file); } -int main() { +int main() +{ Context ctx = createContext(); @@ -77,7 +84,8 @@ int main() { std::future future = promise.get_future(); std::string codeString; - struct Params { + struct Params + { float time; uint32_t screenWidth; uint32_t screenHeight; diff --git a/examples/webgpu_from_scratch/CMakeLists.txt b/examples/webgpu_from_scratch/CMakeLists.txt index 8804628..2c9e57a 100644 --- a/examples/webgpu_from_scratch/CMakeLists.txt +++ b/examples/webgpu_from_scratch/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.11) +cmake_minimum_required(VERSION 3.28) project(wgpu_tutorial) include(FetchContent) diff --git a/setup.py b/setup.py index 4e28348..513ac35 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ def report_progress(block_num, block_size, total_size): nonlocal total_downloaded total_downloaded += block_size print(f"\rDownloaded {total_downloaded // (1024 * 1024)} MB", end="") - + try: ssl._create_default_https_context = ssl._create_stdlib_context urllib.request.urlretrieve(url, output_filename, reporthook=report_progress) @@ -36,54 +36,93 @@ def report_progress(block_num, block_size, total_size): except Exception as e: print(f"\nFailed to download {output_filename}") print(f"Error: {str(e)}") - sys.exit(1) + return False def check_os(os_name): print("\nChecking System") print("===============\n") print(f" Operating System : {os_name}") - supported = {"macOS", "Linux"} + supported = {"macOS", "Linux", "Windows 64-bit", "Windows 32-bit"} if os_name not in supported: print("Unsupported operating system") sys.exit(1) -def download_dawn(os_name): +def download_dawn(os_name, arch, build_type): print("\nDownload Dawn Library") print("=====================\n") + lib_ext = { + "macOS": "dylib", + "Linux": "so", + "Windows 64-bit": "dll", + "Windows 32-bit": "dll", + } + outfile_map = { + "macOS": f"third_party/lib/libdawn_{arch}_{build_type}.dylib", + "Linux": f"third_party/lib/libdawn_{arch}_{build_type}.so", + "Windows 64-bit": f"third_party\\lib\\libdawn_{arch}_{build_type}.dll", + "Windows 32-bit": f"third_party\\lib\\libdawn_{arch}_{build_type}.dll", + } + fallback_map = { "macOS": "third_party/lib/libdawn.dylib", "Linux": "third_party/lib/libdawn.so", + "Windows 64-bit": "third_party\\lib\\libdawn.dll", + "Windows 32-bit": "third_party\\lib\\libdawn.dll", } + url_map = { + "macOS": f"https://github.com/austinvhuang/dawn-artifacts/releases/download/prerelease/libdawn_{arch}_{build_type}.dylib", + "Linux": f"https://github.com/austinvhuang/dawn-artifacts/releases/download/prerelease/libdawn_{arch}_{build_type}.so", + "Windows 64-bit": f"https://github.com/austinvhuang/dawn-artifacts/releases/download/prerelease/libdawn_{arch}_{build_type}.dll", + "Windows 32-bit": f"https://github.com/austinvhuang/dawn-artifacts/releases/download/prerelease/libdawn_{arch}_{build_type}.dll", + } + fallback_url_map = { "macOS": "https://github.com/austinvhuang/dawn-artifacts/releases/download/prerelease/libdawn.dylib", "Linux": "https://github.com/austinvhuang/dawn-artifacts/releases/download/prerelease/libdawn.so", + "Windows 64-bit": "https://github.com/austinvhuang/dawn-artifacts/releases/download/prerelease/libdawn_x64.dll", + "Windows 32-bit": "https://github.com/austinvhuang/dawn-artifacts/releases/download/prerelease/libdawn_x86.dll", } - outfile = outfile_map.get(os_name) + outfile = Path(outfile_map.get(os_name)) + fallback_file = Path(fallback_map.get(os_name)) url = url_map.get(os_name) + fallback_url = fallback_url_map.get(os_name) - if not outfile or not url: - print(f"No download information for {os_name}") - sys.exit(1) + cwd = Path.cwd() + print(f" Output File : {outfile}") + print(f" Current Directory: {cwd}") + print(f" File Exists : {cwd / outfile}") + if outfile.exists(): + print(f" File {outfile} already exists, skipping.") + sys.exit(0) print(f" URL : {url}") print(f" Download File : {outfile}\n") print(" Downloading ...\n") - if Path(outfile).exists(): - print(f" File {outfile} already exists, skipping.") - sys.exit(0) + outfile.parent.mkdir(parents=True, exist_ok=True) + if download_file(url, outfile): + return - Path(outfile).parent.mkdir(parents=True, exist_ok=True) - download_file(url, outfile) + print("\nPrimary file not found, attempting fallback download...\n") + print(f" Fallback URL : {fallback_url}") + print(f" Fallback File : {fallback_file}\n") + + if download_file(fallback_url, fallback_file): + outfile.unlink(missing_ok=True) # Remove partial download if needed + fallback_file.rename(outfile) + return + + print("Failed to download both primary and fallback files.") + sys.exit(1) def setup_env(os_name): print("\nEnvironment Setup") print("=================\n") - current_dir = os.getcwd() - lib_dir = os.path.join(current_dir, "third_party", "lib") + current_dir = Path.cwd() + lib_dir = current_dir / "third_party" / "lib" if os_name == "macOS": print(" Before running the program, run the following command or add it to your shell profile:") @@ -97,13 +136,22 @@ def setup_env(os_name): with open("source", "w") as f: f.write(f"export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:{lib_dir}\n") + if os_name.startswith("Windows"): + print(" Before running the program, add the following path to your PATH environment variable:") + print(f" {lib_dir}") + + with open("source.bat", "w") as f: + f.write(f"set PATH=%PATH%;{lib_dir}\n") def main(): os_name = get_os_name() + arch = "x64" if platform.machine().endswith('64') else "x86" + build_type = "Debug" if 'debug' in sys.argv else "Release" + check_os(os_name) - download_dawn(os_name) + download_dawn(os_name, arch, build_type) setup_env(os_name) print() if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/third_party/fetchcontent/.gitkeep b/third_party/fetchcontent/.gitkeep deleted file mode 100644 index e69de29..0000000