From 36dff0db63102527ff42d4216bc3c933de8531a1 Mon Sep 17 00:00:00 2001 From: fincs Date: Fri, 28 Feb 2020 01:42:12 +0100 Subject: [PATCH] Import deko3d examples --- graphics/deko3d/deko_examples/Makefile | 271 ++++++++++ .../deko_examples/romfs/cat-256x256.bc1 | Bin 0 -> 32768 bytes .../deko3d/deko_examples/romfs/teapot-idx.bin | Bin 0 -> 14784 bytes .../deko3d/deko_examples/romfs/teapot-vtx.bin | Bin 0 -> 31008 bytes .../source/Example01_SimpleSetup.cpp | 176 +++++++ .../source/Example02_Triangle.cpp | 231 +++++++++ .../deko_examples/source/Example03_Cube.cpp | 372 ++++++++++++++ .../source/Example04_TexturedCube.cpp | 414 +++++++++++++++ .../source/Example05_Tessellation.cpp | 253 +++++++++ .../source/Example06_Multisampling.cpp | 408 +++++++++++++++ .../source/Example07_MeshLighting.cpp | 401 +++++++++++++++ .../source/Example08_DeferredShading.cpp | 482 ++++++++++++++++++ .../source/Example09_SimpleCompute.cpp | 318 ++++++++++++ .../source/SampleFramework/CApplication.cpp | 69 +++ .../source/SampleFramework/CApplication.h | 38 ++ .../source/SampleFramework/CCmdMemRing.h | 57 +++ .../source/SampleFramework/CDescriptorSet.h | 71 +++ .../source/SampleFramework/CExternalImage.cpp | 37 ++ .../source/SampleFramework/CExternalImage.h | 37 ++ .../source/SampleFramework/CIntrusiveList.h | 119 +++++ .../source/SampleFramework/CIntrusiveTree.cpp | 214 ++++++++ .../source/SampleFramework/CIntrusiveTree.h | 250 +++++++++ .../source/SampleFramework/CMemPool.cpp | 175 +++++++ .../source/SampleFramework/CMemPool.h | 120 +++++ .../source/SampleFramework/CShader.cpp | 62 +++ .../source/SampleFramework/CShader.h | 31 ++ .../source/SampleFramework/FileLoader.cpp | 27 + .../source/SampleFramework/FileLoader.h | 9 + .../source/SampleFramework/LICENSE | 18 + .../source/SampleFramework/common.h | 12 + .../source/SampleFramework/startup.cpp | 34 ++ .../source/basic_deferred_fsh.glsl | 15 + .../source/basic_lighting_fsh.glsl | 46 ++ .../deko_examples/source/basic_vsh.glsl | 12 + .../deko_examples/source/color_fsh.glsl | 9 + .../deko_examples/source/composition_fsh.glsl | 53 ++ .../deko_examples/source/composition_vsh.glsl | 24 + graphics/deko3d/deko_examples/source/main.cpp | 137 +++++ .../deko3d/deko_examples/source/sinewave.glsl | 38 ++ .../source/tess_simple_tcsh.glsl | 21 + .../source/tess_simple_tesh.glsl | 21 + .../deko_examples/source/texture_fsh.glsl | 11 + .../source/transform_normal_vsh.glsl | 28 + .../deko_examples/source/transform_vsh.glsl | 20 + 44 files changed, 5141 insertions(+) create mode 100644 graphics/deko3d/deko_examples/Makefile create mode 100644 graphics/deko3d/deko_examples/romfs/cat-256x256.bc1 create mode 100644 graphics/deko3d/deko_examples/romfs/teapot-idx.bin create mode 100644 graphics/deko3d/deko_examples/romfs/teapot-vtx.bin create mode 100644 graphics/deko3d/deko_examples/source/Example01_SimpleSetup.cpp create mode 100644 graphics/deko3d/deko_examples/source/Example02_Triangle.cpp create mode 100644 graphics/deko3d/deko_examples/source/Example03_Cube.cpp create mode 100644 graphics/deko3d/deko_examples/source/Example04_TexturedCube.cpp create mode 100644 graphics/deko3d/deko_examples/source/Example05_Tessellation.cpp create mode 100644 graphics/deko3d/deko_examples/source/Example06_Multisampling.cpp create mode 100644 graphics/deko3d/deko_examples/source/Example07_MeshLighting.cpp create mode 100644 graphics/deko3d/deko_examples/source/Example08_DeferredShading.cpp create mode 100644 graphics/deko3d/deko_examples/source/Example09_SimpleCompute.cpp create mode 100644 graphics/deko3d/deko_examples/source/SampleFramework/CApplication.cpp create mode 100644 graphics/deko3d/deko_examples/source/SampleFramework/CApplication.h create mode 100644 graphics/deko3d/deko_examples/source/SampleFramework/CCmdMemRing.h create mode 100644 graphics/deko3d/deko_examples/source/SampleFramework/CDescriptorSet.h create mode 100644 graphics/deko3d/deko_examples/source/SampleFramework/CExternalImage.cpp create mode 100644 graphics/deko3d/deko_examples/source/SampleFramework/CExternalImage.h create mode 100644 graphics/deko3d/deko_examples/source/SampleFramework/CIntrusiveList.h create mode 100644 graphics/deko3d/deko_examples/source/SampleFramework/CIntrusiveTree.cpp create mode 100644 graphics/deko3d/deko_examples/source/SampleFramework/CIntrusiveTree.h create mode 100644 graphics/deko3d/deko_examples/source/SampleFramework/CMemPool.cpp create mode 100644 graphics/deko3d/deko_examples/source/SampleFramework/CMemPool.h create mode 100644 graphics/deko3d/deko_examples/source/SampleFramework/CShader.cpp create mode 100644 graphics/deko3d/deko_examples/source/SampleFramework/CShader.h create mode 100644 graphics/deko3d/deko_examples/source/SampleFramework/FileLoader.cpp create mode 100644 graphics/deko3d/deko_examples/source/SampleFramework/FileLoader.h create mode 100644 graphics/deko3d/deko_examples/source/SampleFramework/LICENSE create mode 100644 graphics/deko3d/deko_examples/source/SampleFramework/common.h create mode 100644 graphics/deko3d/deko_examples/source/SampleFramework/startup.cpp create mode 100644 graphics/deko3d/deko_examples/source/basic_deferred_fsh.glsl create mode 100644 graphics/deko3d/deko_examples/source/basic_lighting_fsh.glsl create mode 100644 graphics/deko3d/deko_examples/source/basic_vsh.glsl create mode 100644 graphics/deko3d/deko_examples/source/color_fsh.glsl create mode 100644 graphics/deko3d/deko_examples/source/composition_fsh.glsl create mode 100644 graphics/deko3d/deko_examples/source/composition_vsh.glsl create mode 100644 graphics/deko3d/deko_examples/source/main.cpp create mode 100644 graphics/deko3d/deko_examples/source/sinewave.glsl create mode 100644 graphics/deko3d/deko_examples/source/tess_simple_tcsh.glsl create mode 100644 graphics/deko3d/deko_examples/source/tess_simple_tesh.glsl create mode 100644 graphics/deko3d/deko_examples/source/texture_fsh.glsl create mode 100644 graphics/deko3d/deko_examples/source/transform_normal_vsh.glsl create mode 100644 graphics/deko3d/deko_examples/source/transform_vsh.glsl diff --git a/graphics/deko3d/deko_examples/Makefile b/graphics/deko3d/deko_examples/Makefile new file mode 100644 index 0000000..d237ed2 --- /dev/null +++ b/graphics/deko3d/deko_examples/Makefile @@ -0,0 +1,271 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") +endif + +TOPDIR ?= $(CURDIR) +include $(DEVKITPRO)/libnx/switch_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +# ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional) +# +# NO_ICON: if set to anything, do not use icon. +# NO_NACP: if set to anything, no .nacp file is generated. +# APP_TITLE is the name of the app stored in the .nacp file (Optional) +# APP_AUTHOR is the author of the app stored in the .nacp file (Optional) +# APP_VERSION is the version of the app stored in the .nacp file (Optional) +# APP_TITLEID is the titleID of the app stored in the .nacp file (Optional) +# ICON is the filename of the icon (.jpg), relative to the project folder. +# If not set, it attempts to use one of the following (in this order): +# - .jpg +# - icon.jpg +# - /default_icon.jpg +# +# CONFIG_JSON is the filename of the NPDM config file (.json), relative to the project folder. +# If not set, it attempts to use one of the following (in this order): +# - .json +# - config.json +# If a JSON file is provided or autodetected, an ExeFS PFS0 (.nsp) is built instead +# of a homebrew executable (.nro). This is intended to be used for sysmodules. +# NACP building is skipped as well. +#--------------------------------------------------------------------------------- +TARGET := $(notdir $(CURDIR)) +BUILD := build +SOURCES := source source/SampleFramework +DATA := data +INCLUDES := include +ROMFS := romfs + +# Output folders for autogenerated files in romfs +OUT_SHADERS := shaders + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE + +CFLAGS := -g -Wall -O2 -ffunction-sections \ + $(ARCH) $(DEFINES) + +CFLAGS += $(INCLUDE) -D__SWITCH__ + +CXXFLAGS := $(CFLAGS) -std=gnu++17 -fno-exceptions -fno-rtti + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := -ldeko3dd -lnx + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(LIBNX) + + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +GLSLFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.glsl))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES_BIN := $(addsuffix .o,$(BINFILES)) +export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) +export OFILES := $(OFILES_BIN) $(OFILES_SRC) +export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) + +ifneq ($(strip $(ROMFS)),) + ROMFS_TARGETS := + ROMFS_FOLDERS := + ifneq ($(strip $(OUT_SHADERS)),) + ROMFS_SHADERS := $(ROMFS)/$(OUT_SHADERS) + ROMFS_TARGETS += $(patsubst %.glsl, $(ROMFS_SHADERS)/%.dksh, $(GLSLFILES)) + ROMFS_FOLDERS += $(ROMFS_SHADERS) + endif + + export ROMFS_DEPS := $(foreach file,$(ROMFS_TARGETS),$(CURDIR)/$(file)) +endif + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +ifeq ($(strip $(CONFIG_JSON)),) + jsons := $(wildcard *.json) + ifneq (,$(findstring $(TARGET).json,$(jsons))) + export APP_JSON := $(TOPDIR)/$(TARGET).json + else + ifneq (,$(findstring config.json,$(jsons))) + export APP_JSON := $(TOPDIR)/config.json + endif + endif +else + export APP_JSON := $(TOPDIR)/$(CONFIG_JSON) +endif + +ifeq ($(strip $(ICON)),) + icons := $(wildcard *.jpg) + ifneq (,$(findstring $(TARGET).jpg,$(icons))) + export APP_ICON := $(TOPDIR)/$(TARGET).jpg + else + ifneq (,$(findstring icon.jpg,$(icons))) + export APP_ICON := $(TOPDIR)/icon.jpg + endif + endif +else + export APP_ICON := $(TOPDIR)/$(ICON) +endif + +ifeq ($(strip $(NO_ICON)),) + export NROFLAGS += --icon=$(APP_ICON) +endif + +ifeq ($(strip $(NO_NACP)),) + export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp +endif + +ifneq ($(APP_TITLEID),) + export NACPFLAGS += --titleid=$(APP_TITLEID) +endif + +ifneq ($(ROMFS),) + export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS) +endif + +.PHONY: all clean + +#--------------------------------------------------------------------------------- +all: $(ROMFS_TARGETS) | $(BUILD) + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +$(BUILD): + @mkdir -p $@ + +ifneq ($(strip $(ROMFS_TARGETS)),) + +$(ROMFS_TARGETS): | $(ROMFS_FOLDERS) + +$(ROMFS_FOLDERS): + @mkdir -p $@ + +$(ROMFS_SHADERS)/%_vsh.dksh: %_vsh.glsl + @echo {vert} $(notdir $<) + @uam -s vert -o $@ $< + +$(ROMFS_SHADERS)/%_tcsh.dksh: %_tcsh.glsl + @echo {tess_ctrl} $(notdir $<) + @uam -s tess_ctrl -o $@ $< + +$(ROMFS_SHADERS)/%_tesh.dksh: %_tesh.glsl + @echo {tess_eval} $(notdir $<) + @uam -s tess_eval -o $@ $< + +$(ROMFS_SHADERS)/%_gsh.dksh: %_gsh.glsl + @echo {geom} $(notdir $<) + @uam -s geom -o $@ $< + +$(ROMFS_SHADERS)/%_fsh.dksh: %_fsh.glsl + @echo {frag} $(notdir $<) + @uam -s frag -o $@ $< + +$(ROMFS_SHADERS)/%.dksh: %.glsl + @echo {comp} $(notdir $<) + @uam -s comp -o $@ $< + +endif + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... +ifeq ($(strip $(APP_JSON)),) + @rm -fr $(BUILD) $(ROMFS_FOLDERS) $(TARGET).nro $(TARGET).nacp $(TARGET).elf +else + @rm -fr $(BUILD) $(ROMFS_FOLDERS) $(TARGET).nsp $(TARGET).nso $(TARGET).npdm $(TARGET).elf +endif + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +ifeq ($(strip $(APP_JSON)),) + +all : $(OUTPUT).nro + +ifeq ($(strip $(NO_NACP)),) +$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp $(ROMFS_DEPS) +else +$(OUTPUT).nro : $(OUTPUT).elf $(ROMFS_DEPS) +endif + +else + +all : $(OUTPUT).nsp + +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm + +$(OUTPUT).nso : $(OUTPUT).elf + +endif + +$(OUTPUT).elf : $(OFILES) + +$(OFILES_SRC) : $(HFILES_BIN) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o %_bin.h : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/graphics/deko3d/deko_examples/romfs/cat-256x256.bc1 b/graphics/deko3d/deko_examples/romfs/cat-256x256.bc1 new file mode 100644 index 0000000000000000000000000000000000000000..b89708a186fae3a9374c62a802ac4fa8d8b14f30 GIT binary patch literal 32768 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�pVcd># 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 TuKFwQXW{sXu>Q8NRJ{Cu231v~ literal 0 HcmV?d00001 diff --git a/graphics/deko3d/deko_examples/source/Example01_SimpleSetup.cpp b/graphics/deko3d/deko_examples/source/Example01_SimpleSetup.cpp new file mode 100644 index 0000000..904dcb3 --- /dev/null +++ b/graphics/deko3d/deko_examples/source/Example01_SimpleSetup.cpp @@ -0,0 +1,176 @@ +/* +** deko3d Example 01: Simple Setup +** This example shows how to setup deko3d for rendering scenes with the GPU. +** New concepts in this example: +** - Creating devices and queues +** - Basic memory management +** - Setting up framebuffers and swapchains +** - Recording a static command list with rendering commands +** - Acquiring and presenting images with the queue and swapchain +*/ + +// Sample Framework headers +#include "SampleFramework/CApplication.h" +#include "SampleFramework/CMemPool.h" + +// C++ standard library headers +#include +#include + +class CExample01 final : public CApplication +{ + static constexpr unsigned NumFramebuffers = 2; + static constexpr uint32_t FramebufferWidth = 1280; + static constexpr uint32_t FramebufferHeight = 720; + static constexpr unsigned StaticCmdSize = 0x1000; + + dk::UniqueDevice device; + dk::UniqueQueue queue; + + std::optional pool_images; + std::optional pool_data; + + dk::UniqueCmdBuf cmdbuf; + + CMemPool::Handle framebuffers_mem[NumFramebuffers]; + dk::Image framebuffers[NumFramebuffers]; + DkCmdList framebuffer_cmdlists[NumFramebuffers]; + dk::UniqueSwapchain swapchain; + + DkCmdList render_cmdlist; + +public: + CExample01() + { + // Create the deko3d device + device = dk::DeviceMaker{}.create(); + + // Create the main queue + queue = dk::QueueMaker{device}.setFlags(DkQueueFlags_Graphics).create(); + + // Create the memory pools + pool_images.emplace(device, DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image, 16*1024*1024); + pool_data.emplace(device, DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached, 1*1024*1024); + + // Create the static command buffer and feed it freshly allocated memory + cmdbuf = dk::CmdBufMaker{device}.create(); + CMemPool::Handle cmdmem = pool_data->allocate(StaticCmdSize); + cmdbuf.addMemory(cmdmem.getMemBlock(), cmdmem.getOffset(), cmdmem.getSize()); + + // Create the framebuffer resources + createFramebufferResources(); + } + + ~CExample01() + { + // Destroy the framebuffer resources + destroyFramebufferResources(); + } + + void createFramebufferResources() + { + // Create layout for the framebuffers + dk::ImageLayout layout_framebuffer; + dk::ImageLayoutMaker{device} + .setFlags(DkImageFlags_UsageRender | DkImageFlags_UsagePresent | DkImageFlags_HwCompression) + .setFormat(DkImageFormat_RGBA8_Unorm) + .setDimensions(FramebufferWidth, FramebufferHeight) + .initialize(layout_framebuffer); + + // Create the framebuffers + std::array fb_array; + uint64_t fb_size = layout_framebuffer.getSize(); + uint32_t fb_align = layout_framebuffer.getAlignment(); + for (unsigned i = 0; i < NumFramebuffers; i ++) + { + // Allocate a framebuffer + framebuffers_mem[i] = pool_images->allocate(fb_size, fb_align); + framebuffers[i].initialize(layout_framebuffer, framebuffers_mem[i].getMemBlock(), framebuffers_mem[i].getOffset()); + + // Generate a command list that binds it + dk::ImageView colorTarget{ framebuffers[i] }; + cmdbuf.bindRenderTargets(&colorTarget); + framebuffer_cmdlists[i] = cmdbuf.finishList(); + + // Fill in the array for use later by the swapchain creation code + fb_array[i] = &framebuffers[i]; + } + + // Create the swapchain using the framebuffers + swapchain = dk::SwapchainMaker{device, nwindowGetDefault(), fb_array}.create(); + + // Generate the main rendering cmdlist + recordStaticCommands(); + } + + void destroyFramebufferResources() + { + // Return early if we have nothing to destroy + if (!swapchain) return; + + // Make sure the queue is idle before destroying anything + queue.waitIdle(); + + // Clear the static cmdbuf, destroying the static cmdlists in the process + cmdbuf.clear(); + + // Destroy the swapchain + swapchain.destroy(); + + // Destroy the framebuffers + for (unsigned i = 0; i < NumFramebuffers; i ++) + framebuffers_mem[i].destroy(); + } + + void recordStaticCommands() + { + // Calculate several measurements for the scene + unsigned HalfWidth = FramebufferWidth/2, HalfHeight = FramebufferHeight/2; + unsigned BoxSize = 400; + unsigned BoxX = HalfWidth - BoxSize/2, BoxY = HalfHeight - BoxSize/2; + unsigned TileWidth = BoxSize/5, TileHeight = BoxSize/4; + + // Draw a scene using only scissors and clear colors + cmdbuf.setScissors(0, { { 0, 0, FramebufferWidth, FramebufferHeight } }); + cmdbuf.clearColor(0, DkColorMask_RGBA, 0.0f, 0.25f, 0.0f, 1.0f); + cmdbuf.setScissors(0, { { BoxX, BoxY, BoxSize, BoxSize } }); + cmdbuf.clearColor(0, DkColorMask_RGBA, 229/255.0f, 1.0f, 232/255.0f, 1.0f); + cmdbuf.setScissors(0, { { BoxX + 2*TileWidth, BoxY + 1*TileHeight, 1*TileWidth, 1*TileHeight } }); + cmdbuf.clearColor(0, DkColorMask_RGBA, 0.0f, 0.5f, 0.0f, 1.0f); + cmdbuf.setScissors(0, { { BoxX + 1*TileWidth, BoxY + 2*TileHeight, 3*TileWidth, 1*TileHeight } }); + cmdbuf.clearColor(0, DkColorMask_RGBA, 0.0f, 0.5f, 0.0f, 1.0f); + render_cmdlist = cmdbuf.finishList(); + } + + void render() + { + // Acquire a framebuffer from the swapchain (and wait for it to be available) + int slot = queue.acquireImage(swapchain); + + // Run the command list that attaches said framebuffer to the queue + queue.submitCommands(framebuffer_cmdlists[slot]); + + // Run the main rendering command list + queue.submitCommands(render_cmdlist); + + // Now that we are done rendering, present it to the screen + queue.presentImage(swapchain, slot); + } + + bool onFrame(u64 ns) override + { + hidScanInput(); + u64 kDown = hidKeysDown(CONTROLLER_P1_AUTO); + if (kDown & KEY_PLUS) + return false; + + render(); + return true; + } +}; + +void Example01(void) +{ + CExample01 app; + app.run(); +} diff --git a/graphics/deko3d/deko_examples/source/Example02_Triangle.cpp b/graphics/deko3d/deko_examples/source/Example02_Triangle.cpp new file mode 100644 index 0000000..97eaa35 --- /dev/null +++ b/graphics/deko3d/deko_examples/source/Example02_Triangle.cpp @@ -0,0 +1,231 @@ +/* +** deko3d Example 02: Triangle +** This example shows how to draw a basic multi-colored triangle. +** New concepts in this example: +** - Loading and using shaders +** - Setting up basic 3D engine state +** - Setting up vertex attributes and vertex buffers +** - Drawing primitives +*/ + +// Sample Framework headers +#include "SampleFramework/CApplication.h" +#include "SampleFramework/CMemPool.h" +#include "SampleFramework/CShader.h" + +// C++ standard library headers +#include +#include + +namespace +{ + struct Vertex + { + float position[3]; + float color[3]; + }; + + constexpr std::array VertexAttribState = + { + DkVtxAttribState{ 0, 0, offsetof(Vertex, position), DkVtxAttribSize_3x32, DkVtxAttribType_Float, 0 }, + DkVtxAttribState{ 0, 0, offsetof(Vertex, color), DkVtxAttribSize_3x32, DkVtxAttribType_Float, 0 }, + }; + + constexpr std::array VertexBufferState = + { + DkVtxBufferState{ sizeof(Vertex), 0 }, + }; + + constexpr std::array TriangleVertexData = + { + Vertex{ { 0.0f, +1.0f, 0.0f }, { 1.0f, 0.0f, 0.0f } }, + Vertex{ { -1.0f, -1.0f, 0.0f }, { 0.0f, 1.0f, 0.0f } }, + Vertex{ { +1.0f, -1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } }, + }; +} + +class CExample02 final : public CApplication +{ + static constexpr unsigned NumFramebuffers = 2; + static constexpr uint32_t FramebufferWidth = 1280; + static constexpr uint32_t FramebufferHeight = 720; + static constexpr unsigned StaticCmdSize = 0x10000; + + dk::UniqueDevice device; + dk::UniqueQueue queue; + + std::optional pool_images; + std::optional pool_code; + std::optional pool_data; + + dk::UniqueCmdBuf cmdbuf; + + CShader vertexShader; + CShader fragmentShader; + + CMemPool::Handle vertexBuffer; + + CMemPool::Handle framebuffers_mem[NumFramebuffers]; + dk::Image framebuffers[NumFramebuffers]; + DkCmdList framebuffer_cmdlists[NumFramebuffers]; + dk::UniqueSwapchain swapchain; + + DkCmdList render_cmdlist; + +public: + CExample02() + { + // Create the deko3d device + device = dk::DeviceMaker{}.create(); + + // Create the main queue + queue = dk::QueueMaker{device}.setFlags(DkQueueFlags_Graphics).create(); + + // Create the memory pools + pool_images.emplace(device, DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image, 16*1024*1024); + pool_code.emplace(device, DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached | DkMemBlockFlags_Code, 128*1024); + pool_data.emplace(device, DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached, 1*1024*1024); + + // Create the static command buffer and feed it freshly allocated memory + cmdbuf = dk::CmdBufMaker{device}.create(); + CMemPool::Handle cmdmem = pool_data->allocate(StaticCmdSize); + cmdbuf.addMemory(cmdmem.getMemBlock(), cmdmem.getOffset(), cmdmem.getSize()); + + // Load the shaders + vertexShader.load(*pool_code, "romfs:/shaders/basic_vsh.dksh"); + fragmentShader.load(*pool_code, "romfs:/shaders/color_fsh.dksh"); + + // Load the vertex buffer + vertexBuffer = pool_data->allocate(sizeof(TriangleVertexData), alignof(Vertex)); + memcpy(vertexBuffer.getCpuAddr(), TriangleVertexData.data(), vertexBuffer.getSize()); + + // Create the framebuffer resources + createFramebufferResources(); + } + + ~CExample02() + { + // Destroy the framebuffer resources + destroyFramebufferResources(); + + // Destroy the vertex buffer (not strictly needed in this case) + vertexBuffer.destroy(); + } + + void createFramebufferResources() + { + // Create layout for the framebuffers + dk::ImageLayout layout_framebuffer; + dk::ImageLayoutMaker{device} + .setFlags(DkImageFlags_UsageRender | DkImageFlags_UsagePresent | DkImageFlags_HwCompression) + .setFormat(DkImageFormat_RGBA8_Unorm) + .setDimensions(FramebufferWidth, FramebufferHeight) + .initialize(layout_framebuffer); + + // Create the framebuffers + std::array fb_array; + uint64_t fb_size = layout_framebuffer.getSize(); + uint32_t fb_align = layout_framebuffer.getAlignment(); + for (unsigned i = 0; i < NumFramebuffers; i ++) + { + // Allocate a framebuffer + framebuffers_mem[i] = pool_images->allocate(fb_size, fb_align); + framebuffers[i].initialize(layout_framebuffer, framebuffers_mem[i].getMemBlock(), framebuffers_mem[i].getOffset()); + + // Generate a command list that binds it + dk::ImageView colorTarget{ framebuffers[i] }; + cmdbuf.bindRenderTargets(&colorTarget); + framebuffer_cmdlists[i] = cmdbuf.finishList(); + + // Fill in the array for use later by the swapchain creation code + fb_array[i] = &framebuffers[i]; + } + + // Create the swapchain using the framebuffers + swapchain = dk::SwapchainMaker{device, nwindowGetDefault(), fb_array}.create(); + + // Generate the main rendering cmdlist + recordStaticCommands(); + } + + void destroyFramebufferResources() + { + // Return early if we have nothing to destroy + if (!swapchain) return; + + // Make sure the queue is idle before destroying anything + queue.waitIdle(); + + // Clear the static cmdbuf, destroying the static cmdlists in the process + cmdbuf.clear(); + + // Destroy the swapchain + swapchain.destroy(); + + // Destroy the framebuffers + for (unsigned i = 0; i < NumFramebuffers; i ++) + framebuffers_mem[i].destroy(); + } + + void recordStaticCommands() + { + // Initialize state structs with deko3d defaults + dk::RasterizerState rasterizerState; + dk::ColorState colorState; + dk::ColorWriteState colorWriteState; + + // Configure viewport and scissor + cmdbuf.setViewports(0, { { 0.0f, 0.0f, FramebufferWidth, FramebufferHeight, 0.0f, 1.0f } }); + cmdbuf.setScissors(0, { { 0, 0, FramebufferWidth, FramebufferHeight } }); + + // Clear the color buffer + cmdbuf.clearColor(0, DkColorMask_RGBA, 0.0f, 0.0f, 0.0f, 0.0f); + + // Bind state required for drawing the triangle + cmdbuf.bindShaders(DkStageFlag_GraphicsMask, { vertexShader, fragmentShader }); + cmdbuf.bindRasterizerState(rasterizerState); + cmdbuf.bindColorState(colorState); + cmdbuf.bindColorWriteState(colorWriteState); + cmdbuf.bindVtxBuffer(0, vertexBuffer.getGpuAddr(), vertexBuffer.getSize()); + cmdbuf.bindVtxAttribState(VertexAttribState); + cmdbuf.bindVtxBufferState(VertexBufferState); + + // Draw the triangle + cmdbuf.draw(DkPrimitive_Triangles, TriangleVertexData.size(), 1, 0, 0); + + // Finish off this command list + render_cmdlist = cmdbuf.finishList(); + } + + void render() + { + // Acquire a framebuffer from the swapchain (and wait for it to be available) + int slot = queue.acquireImage(swapchain); + + // Run the command list that attaches said framebuffer to the queue + queue.submitCommands(framebuffer_cmdlists[slot]); + + // Run the main rendering command list + queue.submitCommands(render_cmdlist); + + // Now that we are done rendering, present it to the screen + queue.presentImage(swapchain, slot); + } + + bool onFrame(u64 ns) override + { + hidScanInput(); + u64 kDown = hidKeysDown(CONTROLLER_P1_AUTO); + if (kDown & KEY_PLUS) + return false; + + render(); + return true; + } +}; + +void Example02(void) +{ + CExample02 app; + app.run(); +} diff --git a/graphics/deko3d/deko_examples/source/Example03_Cube.cpp b/graphics/deko3d/deko_examples/source/Example03_Cube.cpp new file mode 100644 index 0000000..18effe0 --- /dev/null +++ b/graphics/deko3d/deko_examples/source/Example03_Cube.cpp @@ -0,0 +1,372 @@ +/* +** deko3d Example 03: Cube +** This example shows how to draw a basic rotating cube. +** New concepts in this example: +** - Setting up and using a depth buffer +** - Setting up uniform buffers +** - Basic 3D maths, including projection matrices +** - Updating uniforms with a dynamic command buffer +** - Adjusting resolution dynamically by recreating resources (720p handheld/1080p docked) +** - Depth buffer discard after a barrier +*/ + +// Sample Framework headers +#include "SampleFramework/CApplication.h" +#include "SampleFramework/CMemPool.h" +#include "SampleFramework/CShader.h" +#include "SampleFramework/CCmdMemRing.h" + +// C++ standard library headers +#include +#include + +// GLM headers +#define GLM_FORCE_DEFAULT_ALIGNED_GENTYPES // Enforces GLSL std140/std430 alignment rules for glm types +#define GLM_FORCE_INTRINSICS // Enables usage of SIMD CPU instructions (requiring the above as well) +#include +#include +#include +#include + +namespace +{ + struct Vertex + { + float position[3]; + float color[3]; + }; + + constexpr std::array VertexAttribState = + { + DkVtxAttribState{ 0, 0, offsetof(Vertex, position), DkVtxAttribSize_3x32, DkVtxAttribType_Float, 0 }, + DkVtxAttribState{ 0, 0, offsetof(Vertex, color), DkVtxAttribSize_3x32, DkVtxAttribType_Float, 0 }, + }; + + constexpr std::array VertexBufferState = + { + DkVtxBufferState{ sizeof(Vertex), 0 }, + }; + + constexpr std::array CubeVertexData = + { + // +X face + Vertex{ { +1.0f, +1.0f, +1.0f }, { 1.0f, 0.0f, 0.0f } }, + Vertex{ { +1.0f, -1.0f, +1.0f }, { 0.0f, 1.0f, 0.0f } }, + Vertex{ { +1.0f, -1.0f, -1.0f }, { 0.0f, 0.0f, 1.0f } }, + Vertex{ { +1.0f, +1.0f, -1.0f }, { 1.0f, 1.0f, 0.0f } }, + + // -X face + Vertex{ { -1.0f, +1.0f, -1.0f }, { 1.0f, 0.0f, 0.0f } }, + Vertex{ { -1.0f, -1.0f, -1.0f }, { 0.0f, 1.0f, 0.0f } }, + Vertex{ { -1.0f, -1.0f, +1.0f }, { 0.0f, 0.0f, 1.0f } }, + Vertex{ { -1.0f, +1.0f, +1.0f }, { 1.0f, 1.0f, 0.0f } }, + + // +Y face + Vertex{ { -1.0f, +1.0f, -1.0f }, { 1.0f, 0.0f, 0.0f } }, + Vertex{ { -1.0f, +1.0f, +1.0f }, { 0.0f, 1.0f, 0.0f } }, + Vertex{ { +1.0f, +1.0f, +1.0f }, { 0.0f, 0.0f, 1.0f } }, + Vertex{ { +1.0f, +1.0f, -1.0f }, { 1.0f, 1.0f, 0.0f } }, + + // -Y face + Vertex{ { -1.0f, -1.0f, +1.0f }, { 1.0f, 0.0f, 0.0f } }, + Vertex{ { -1.0f, -1.0f, -1.0f }, { 0.0f, 1.0f, 0.0f } }, + Vertex{ { +1.0f, -1.0f, -1.0f }, { 0.0f, 0.0f, 1.0f } }, + Vertex{ { +1.0f, -1.0f, +1.0f }, { 1.0f, 1.0f, 0.0f } }, + + // +Z face + Vertex{ { -1.0f, +1.0f, +1.0f }, { 1.0f, 0.0f, 0.0f } }, + Vertex{ { -1.0f, -1.0f, +1.0f }, { 0.0f, 1.0f, 0.0f } }, + Vertex{ { +1.0f, -1.0f, +1.0f }, { 0.0f, 0.0f, 1.0f } }, + Vertex{ { +1.0f, +1.0f, +1.0f }, { 1.0f, 1.0f, 0.0f } }, + + // -Z face + Vertex{ { +1.0f, +1.0f, -1.0f }, { 1.0f, 0.0f, 0.0f } }, + Vertex{ { +1.0f, -1.0f, -1.0f }, { 0.0f, 1.0f, 0.0f } }, + Vertex{ { -1.0f, -1.0f, -1.0f }, { 0.0f, 0.0f, 1.0f } }, + Vertex{ { -1.0f, +1.0f, -1.0f }, { 1.0f, 1.0f, 0.0f } }, + }; + + struct Transformation + { + glm::mat4 mdlvMtx; + glm::mat4 projMtx; + }; + + inline float fractf(float x) + { + return x - floorf(x); + } +} + +class CExample03 final : public CApplication +{ + static constexpr unsigned NumFramebuffers = 2; + static constexpr unsigned StaticCmdSize = 0x10000; + static constexpr unsigned DynamicCmdSize = 0x10000; + + dk::UniqueDevice device; + dk::UniqueQueue queue; + + std::optional pool_images; + std::optional pool_code; + std::optional pool_data; + + dk::UniqueCmdBuf cmdbuf; + dk::UniqueCmdBuf dyncmd; + CCmdMemRing dynmem; + + CShader vertexShader; + CShader fragmentShader; + + Transformation transformState; + CMemPool::Handle transformUniformBuffer; + + CMemPool::Handle vertexBuffer; + + uint32_t framebufferWidth; + uint32_t framebufferHeight; + + CMemPool::Handle depthBuffer_mem; + CMemPool::Handle framebuffers_mem[NumFramebuffers]; + + dk::Image depthBuffer; + dk::Image framebuffers[NumFramebuffers]; + DkCmdList framebuffer_cmdlists[NumFramebuffers]; + dk::UniqueSwapchain swapchain; + + DkCmdList render_cmdlist; + +public: + CExample03() + { + // Create the deko3d device + device = dk::DeviceMaker{}.create(); + + // Create the main queue + queue = dk::QueueMaker{device}.setFlags(DkQueueFlags_Graphics).create(); + + // Create the memory pools + pool_images.emplace(device, DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image, 16*1024*1024); + pool_code.emplace(device, DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached | DkMemBlockFlags_Code, 128*1024); + pool_data.emplace(device, DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached, 1*1024*1024); + + // Create the static command buffer and feed it freshly allocated memory + cmdbuf = dk::CmdBufMaker{device}.create(); + CMemPool::Handle cmdmem = pool_data->allocate(StaticCmdSize); + cmdbuf.addMemory(cmdmem.getMemBlock(), cmdmem.getOffset(), cmdmem.getSize()); + + // Create the dynamic command buffer and allocate memory for it + dyncmd = dk::CmdBufMaker{device}.create(); + dynmem.allocate(*pool_data, DynamicCmdSize); + + // Load the shaders + vertexShader.load(*pool_code, "romfs:/shaders/transform_vsh.dksh"); + fragmentShader.load(*pool_code, "romfs:/shaders/color_fsh.dksh"); + + // Create the transformation uniform buffer + transformUniformBuffer = pool_data->allocate(sizeof(transformState), DK_UNIFORM_BUF_ALIGNMENT); + + // Load the vertex buffer + vertexBuffer = pool_data->allocate(sizeof(CubeVertexData), alignof(Vertex)); + memcpy(vertexBuffer.getCpuAddr(), CubeVertexData.data(), vertexBuffer.getSize()); + } + + ~CExample03() + { + // Destroy the framebuffer resources + destroyFramebufferResources(); + + // Destroy the vertex buffer (not strictly needed in this case) + vertexBuffer.destroy(); + + // Destroy the uniform buffer (not strictly needed in this case) + transformUniformBuffer.destroy(); + } + + void createFramebufferResources() + { + // Create layout for the depth buffer + dk::ImageLayout layout_depthbuffer; + dk::ImageLayoutMaker{device} + .setFlags(DkImageFlags_UsageRender | DkImageFlags_HwCompression) + .setFormat(DkImageFormat_Z24S8) + .setDimensions(framebufferWidth, framebufferHeight) + .initialize(layout_depthbuffer); + + // Create the depth buffer + depthBuffer_mem = pool_images->allocate(layout_depthbuffer.getSize(), layout_depthbuffer.getAlignment()); + depthBuffer.initialize(layout_depthbuffer, depthBuffer_mem.getMemBlock(), depthBuffer_mem.getOffset()); + + // Create layout for the framebuffers + dk::ImageLayout layout_framebuffer; + dk::ImageLayoutMaker{device} + .setFlags(DkImageFlags_UsageRender | DkImageFlags_UsagePresent | DkImageFlags_HwCompression) + .setFormat(DkImageFormat_RGBA8_Unorm) + .setDimensions(framebufferWidth, framebufferHeight) + .initialize(layout_framebuffer); + + // Create the framebuffers + std::array fb_array; + uint64_t fb_size = layout_framebuffer.getSize(); + uint32_t fb_align = layout_framebuffer.getAlignment(); + for (unsigned i = 0; i < NumFramebuffers; i ++) + { + // Allocate a framebuffer + framebuffers_mem[i] = pool_images->allocate(fb_size, fb_align); + framebuffers[i].initialize(layout_framebuffer, framebuffers_mem[i].getMemBlock(), framebuffers_mem[i].getOffset()); + + // Generate a command list that binds it + dk::ImageView colorTarget{ framebuffers[i] }, depthTarget{ depthBuffer }; + cmdbuf.bindRenderTargets(&colorTarget, &depthTarget); + framebuffer_cmdlists[i] = cmdbuf.finishList(); + + // Fill in the array for use later by the swapchain creation code + fb_array[i] = &framebuffers[i]; + } + + // Create the swapchain using the framebuffers + swapchain = dk::SwapchainMaker{device, nwindowGetDefault(), fb_array}.create(); + + // Generate the main rendering cmdlist + recordStaticCommands(); + + // Initialize the projection matrix + transformState.projMtx = glm::perspectiveRH_ZO( + glm::radians(40.0f), + float(framebufferWidth)/float(framebufferHeight), + 0.01f, 1000.0f); + } + + void destroyFramebufferResources() + { + // Return early if we have nothing to destroy + if (!swapchain) return; + + // Make sure the queue is idle before destroying anything + queue.waitIdle(); + + // Clear the static cmdbuf, destroying the static cmdlists in the process + cmdbuf.clear(); + + // Destroy the swapchain + swapchain.destroy(); + + // Destroy the framebuffers + for (unsigned i = 0; i < NumFramebuffers; i ++) + framebuffers_mem[i].destroy(); + + // Destroy the depth buffer + depthBuffer_mem.destroy(); + } + + void recordStaticCommands() + { + // Initialize state structs with deko3d defaults + dk::RasterizerState rasterizerState; + dk::ColorState colorState; + dk::ColorWriteState colorWriteState; + dk::DepthStencilState depthStencilState; + + // Configure viewport and scissor + cmdbuf.setViewports(0, { { 0.0f, 0.0f, (float)framebufferWidth, (float)framebufferHeight, 0.0f, 1.0f } }); + cmdbuf.setScissors(0, { { 0, 0, framebufferWidth, framebufferHeight } }); + + // Clear the color and depth buffers + cmdbuf.clearColor(0, DkColorMask_RGBA, 0.0f, 0.0f, 0.0f, 0.0f); + cmdbuf.clearDepthStencil(true, 1.0f, 0xFF, 0); + + // Bind state required for drawing the cube + cmdbuf.bindShaders(DkStageFlag_GraphicsMask, { vertexShader, fragmentShader }); + cmdbuf.bindUniformBuffer(DkStage_Vertex, 0, transformUniformBuffer.getGpuAddr(), transformUniformBuffer.getSize()); + cmdbuf.bindRasterizerState(rasterizerState); + cmdbuf.bindColorState(colorState); + cmdbuf.bindColorWriteState(colorWriteState); + cmdbuf.bindDepthStencilState(depthStencilState); + cmdbuf.bindVtxBuffer(0, vertexBuffer.getGpuAddr(), vertexBuffer.getSize()); + cmdbuf.bindVtxAttribState(VertexAttribState); + cmdbuf.bindVtxBufferState(VertexBufferState); + + // Draw the cube + cmdbuf.draw(DkPrimitive_Quads, CubeVertexData.size(), 1, 0, 0); + + // Fragment barrier, to make sure we finish previous work before discarding the depth buffer + cmdbuf.barrier(DkBarrier_Fragments, 0); + + // Discard the depth buffer since we don't need it anymore + cmdbuf.discardDepthStencil(); + + // Finish off this command list + render_cmdlist = cmdbuf.finishList(); + } + + void render() + { + // Begin generating the dynamic command list, for commands that need to be sent only this frame specifically + dynmem.begin(dyncmd); + + // Update the uniform buffer with the new transformation state (this data gets inlined in the command list) + dyncmd.pushConstants( + transformUniformBuffer.getGpuAddr(), transformUniformBuffer.getSize(), + 0, sizeof(transformState), &transformState); + + // Finish off the dynamic command list, and submit it to the queue + queue.submitCommands(dynmem.end(dyncmd)); + + // Acquire a framebuffer from the swapchain (and wait for it to be available) + int slot = queue.acquireImage(swapchain); + + // Run the command list that attaches said framebuffer to the queue + queue.submitCommands(framebuffer_cmdlists[slot]); + + // Run the main rendering command list + queue.submitCommands(render_cmdlist); + + // Now that we are done rendering, present it to the screen + queue.presentImage(swapchain, slot); + } + + void onOperationMode(AppletOperationMode mode) override + { + // Destroy the framebuffer resources + destroyFramebufferResources(); + + // Choose framebuffer size + chooseFramebufferSize(framebufferWidth, framebufferHeight, mode); + + // Recreate the framebuffers and its associated resources + createFramebufferResources(); + } + + bool onFrame(u64 ns) override + { + hidScanInput(); + u64 kDown = hidKeysDown(CONTROLLER_P1_AUTO); + if (kDown & KEY_PLUS) + return false; + + float time = ns / 1000000000.0; // double precision division; followed by implicit cast to single precision + float tau = glm::two_pi(); + + float period1 = fractf(time/8.0f); + float period2 = fractf(time/4.0f); + + // Generate the model-view matrix for this frame + // Keep in mind that GLM transformation functions multiply to the right, so essentially we have: + // mdlvMtx = Translate * RotateX * RotateY * Scale + // This means that the Scale operation is applied first, then RotateY, and so on. + transformState.mdlvMtx = glm::mat4{1.0f}; + transformState.mdlvMtx = glm::translate(transformState.mdlvMtx, glm::vec3{0.0f, 0.0f, -3.0f}); + transformState.mdlvMtx = glm::rotate(transformState.mdlvMtx, sinf(period2 * tau) * tau / 8.0f, glm::vec3{1.0f, 0.0f, 0.0f}); + transformState.mdlvMtx = glm::rotate(transformState.mdlvMtx, -period1 * tau, glm::vec3{0.0f, 1.0f, 0.0f}); + transformState.mdlvMtx = glm::scale(transformState.mdlvMtx, glm::vec3{0.5f}); + + render(); + return true; + } +}; + +void Example03(void) +{ + CExample03 app; + app.run(); +} diff --git a/graphics/deko3d/deko_examples/source/Example04_TexturedCube.cpp b/graphics/deko3d/deko_examples/source/Example04_TexturedCube.cpp new file mode 100644 index 0000000..c1a5962 --- /dev/null +++ b/graphics/deko3d/deko_examples/source/Example04_TexturedCube.cpp @@ -0,0 +1,414 @@ +/* +** deko3d Example 04: Textured Cube +** This example shows how to render a textured cube. +** New concepts in this example: +** - Loading a texture image from the filesystem +** - Creating and using image descriptors +** - Creating and using samplers and sampler descriptors +** - Calculating combined image+sampler handles for use by shaders +** - Initializing persistent state in a queue +** +** The texture used in this example was borrowed from https://pixabay.com/photos/cat-animal-pet-cats-close-up-300572/ +*/ + +// Sample Framework headers +#include "SampleFramework/CApplication.h" +#include "SampleFramework/CMemPool.h" +#include "SampleFramework/CShader.h" +#include "SampleFramework/CCmdMemRing.h" +#include "SampleFramework/CDescriptorSet.h" +#include "SampleFramework/CExternalImage.h" + +// C++ standard library headers +#include +#include + +// GLM headers +#define GLM_FORCE_DEFAULT_ALIGNED_GENTYPES // Enforces GLSL std140/std430 alignment rules for glm types +#define GLM_FORCE_INTRINSICS // Enables usage of SIMD CPU instructions (requiring the above as well) +#include +#include +#include +#include + +namespace +{ + struct Vertex + { + float position[3]; + float texcoord[2]; + }; + + constexpr std::array VertexAttribState = + { + DkVtxAttribState{ 0, 0, offsetof(Vertex, position), DkVtxAttribSize_3x32, DkVtxAttribType_Float, 0 }, + DkVtxAttribState{ 0, 0, offsetof(Vertex, texcoord), DkVtxAttribSize_2x32, DkVtxAttribType_Float, 0 }, + }; + + constexpr std::array VertexBufferState = + { + DkVtxBufferState{ sizeof(Vertex), 0 }, + }; + + constexpr std::array CubeVertexData = + { + // +X face + Vertex{ { +1.0f, +1.0f, +1.0f }, { 0.0f, 0.0f } }, + Vertex{ { +1.0f, -1.0f, +1.0f }, { 0.0f, 1.0f } }, + Vertex{ { +1.0f, -1.0f, -1.0f }, { 1.0f, 1.0f } }, + Vertex{ { +1.0f, +1.0f, -1.0f }, { 1.0f, 0.0f } }, + + // -X face + Vertex{ { -1.0f, +1.0f, -1.0f }, { 0.0f, 0.0f } }, + Vertex{ { -1.0f, -1.0f, -1.0f }, { 0.0f, 1.0f } }, + Vertex{ { -1.0f, -1.0f, +1.0f }, { 1.0f, 1.0f } }, + Vertex{ { -1.0f, +1.0f, +1.0f }, { 1.0f, 0.0f } }, + + // +Y face + Vertex{ { -1.0f, +1.0f, -1.0f }, { 0.0f, 0.0f } }, + Vertex{ { -1.0f, +1.0f, +1.0f }, { 0.0f, 1.0f } }, + Vertex{ { +1.0f, +1.0f, +1.0f }, { 1.0f, 1.0f } }, + Vertex{ { +1.0f, +1.0f, -1.0f }, { 1.0f, 0.0f } }, + + // -Y face + Vertex{ { -1.0f, -1.0f, +1.0f }, { 0.0f, 0.0f } }, + Vertex{ { -1.0f, -1.0f, -1.0f }, { 0.0f, 1.0f } }, + Vertex{ { +1.0f, -1.0f, -1.0f }, { 1.0f, 1.0f } }, + Vertex{ { +1.0f, -1.0f, +1.0f }, { 1.0f, 0.0f } }, + + // +Z face + Vertex{ { -1.0f, +1.0f, +1.0f }, { 0.0f, 0.0f } }, + Vertex{ { -1.0f, -1.0f, +1.0f }, { 0.0f, 1.0f } }, + Vertex{ { +1.0f, -1.0f, +1.0f }, { 1.0f, 1.0f } }, + Vertex{ { +1.0f, +1.0f, +1.0f }, { 1.0f, 0.0f } }, + + // -Z face + Vertex{ { +1.0f, +1.0f, -1.0f }, { 0.0f, 0.0f } }, + Vertex{ { +1.0f, -1.0f, -1.0f }, { 0.0f, 1.0f } }, + Vertex{ { -1.0f, -1.0f, -1.0f }, { 1.0f, 1.0f } }, + Vertex{ { -1.0f, +1.0f, -1.0f }, { 1.0f, 0.0f } }, + }; + + struct Transformation + { + glm::mat4 mdlvMtx; + glm::mat4 projMtx; + }; + + inline float fractf(float x) + { + return x - floorf(x); + } +} + +class CExample04 final : public CApplication +{ + static constexpr unsigned NumFramebuffers = 2; + static constexpr unsigned StaticCmdSize = 0x10000; + static constexpr unsigned DynamicCmdSize = 0x10000; + static constexpr unsigned MaxImages = 1; + static constexpr unsigned MaxSamplers = 1; + + dk::UniqueDevice device; + dk::UniqueQueue queue; + + std::optional pool_images; + std::optional pool_code; + std::optional pool_data; + + dk::UniqueCmdBuf cmdbuf; + dk::UniqueCmdBuf dyncmd; + CCmdMemRing dynmem; + + CDescriptorSet imageDescriptorSet; + CDescriptorSet samplerDescriptorSet; + + CShader vertexShader; + CShader fragmentShader; + + Transformation transformState; + CMemPool::Handle transformUniformBuffer; + + CMemPool::Handle vertexBuffer; + CExternalImage texImage; + + uint32_t framebufferWidth; + uint32_t framebufferHeight; + + CMemPool::Handle depthBuffer_mem; + CMemPool::Handle framebuffers_mem[NumFramebuffers]; + + dk::Image depthBuffer; + dk::Image framebuffers[NumFramebuffers]; + DkCmdList framebuffer_cmdlists[NumFramebuffers]; + dk::UniqueSwapchain swapchain; + + DkCmdList render_cmdlist; + +public: + CExample04() + { + // Create the deko3d device + device = dk::DeviceMaker{}.create(); + + // Create the main queue + queue = dk::QueueMaker{device}.setFlags(DkQueueFlags_Graphics).create(); + + // Create the memory pools + pool_images.emplace(device, DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image, 16*1024*1024); + pool_code.emplace(device, DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached | DkMemBlockFlags_Code, 128*1024); + pool_data.emplace(device, DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached, 1*1024*1024); + + // Create the static command buffer and feed it freshly allocated memory + cmdbuf = dk::CmdBufMaker{device}.create(); + CMemPool::Handle cmdmem = pool_data->allocate(StaticCmdSize); + cmdbuf.addMemory(cmdmem.getMemBlock(), cmdmem.getOffset(), cmdmem.getSize()); + + // Create the dynamic command buffer and allocate memory for it + dyncmd = dk::CmdBufMaker{device}.create(); + dynmem.allocate(*pool_data, DynamicCmdSize); + + // Create the image and sampler descriptor sets + imageDescriptorSet.allocate(*pool_data); + samplerDescriptorSet.allocate(*pool_data); + + // Load the shaders + vertexShader.load(*pool_code, "romfs:/shaders/transform_vsh.dksh"); + fragmentShader.load(*pool_code, "romfs:/shaders/texture_fsh.dksh"); + + // Create the transformation uniform buffer + transformUniformBuffer = pool_data->allocate(sizeof(transformState), DK_UNIFORM_BUF_ALIGNMENT); + + // Load the vertex buffer + vertexBuffer = pool_data->allocate(sizeof(CubeVertexData), alignof(Vertex)); + memcpy(vertexBuffer.getCpuAddr(), CubeVertexData.data(), vertexBuffer.getSize()); + + // Load the image + texImage.load(*pool_images, *pool_data, device, queue, "romfs:/cat-256x256.bc1", 256, 256, DkImageFormat_RGB_BC1); + + // Configure persistent state in the queue + { + // Upload the image descriptor + imageDescriptorSet.update(cmdbuf, 0, texImage.getDescriptor()); + + // Configure a sampler + dk::Sampler sampler; + sampler.setFilter(DkFilter_Linear, DkFilter_Linear); + sampler.setWrapMode(DkWrapMode_ClampToEdge, DkWrapMode_ClampToEdge, DkWrapMode_ClampToEdge); + + // Upload the sampler descriptor + dk::SamplerDescriptor samplerDescriptor; + samplerDescriptor.initialize(sampler); + samplerDescriptorSet.update(cmdbuf, 0, samplerDescriptor); + + // Bind the image and sampler descriptor sets + imageDescriptorSet.bindForImages(cmdbuf); + samplerDescriptorSet.bindForSamplers(cmdbuf); + + // Submit the configuration commands to the queue + queue.submitCommands(cmdbuf.finishList()); + queue.waitIdle(); + cmdbuf.clear(); + } + } + + ~CExample04() + { + // Destroy the framebuffer resources + destroyFramebufferResources(); + + // Destroy the vertex buffer (not strictly needed in this case) + vertexBuffer.destroy(); + + // Destroy the uniform buffer (not strictly needed in this case) + transformUniformBuffer.destroy(); + } + + void createFramebufferResources() + { + // Create layout for the depth buffer + dk::ImageLayout layout_depthbuffer; + dk::ImageLayoutMaker{device} + .setFlags(DkImageFlags_UsageRender | DkImageFlags_HwCompression) + .setFormat(DkImageFormat_Z24S8) + .setDimensions(framebufferWidth, framebufferHeight) + .initialize(layout_depthbuffer); + + // Create the depth buffer + depthBuffer_mem = pool_images->allocate(layout_depthbuffer.getSize(), layout_depthbuffer.getAlignment()); + depthBuffer.initialize(layout_depthbuffer, depthBuffer_mem.getMemBlock(), depthBuffer_mem.getOffset()); + + // Create layout for the framebuffers + dk::ImageLayout layout_framebuffer; + dk::ImageLayoutMaker{device} + .setFlags(DkImageFlags_UsageRender | DkImageFlags_UsagePresent | DkImageFlags_HwCompression) + .setFormat(DkImageFormat_RGBA8_Unorm) + .setDimensions(framebufferWidth, framebufferHeight) + .initialize(layout_framebuffer); + + // Create the framebuffers + std::array fb_array; + uint64_t fb_size = layout_framebuffer.getSize(); + uint32_t fb_align = layout_framebuffer.getAlignment(); + for (unsigned i = 0; i < NumFramebuffers; i ++) + { + // Allocate a framebuffer + framebuffers_mem[i] = pool_images->allocate(fb_size, fb_align); + framebuffers[i].initialize(layout_framebuffer, framebuffers_mem[i].getMemBlock(), framebuffers_mem[i].getOffset()); + + // Generate a command list that binds it + dk::ImageView colorTarget{ framebuffers[i] }, depthTarget { depthBuffer }; + cmdbuf.bindRenderTargets(&colorTarget, &depthTarget); + framebuffer_cmdlists[i] = cmdbuf.finishList(); + + // Fill in the array for use later by the swapchain creation code + fb_array[i] = &framebuffers[i]; + } + + // Create the swapchain using the framebuffers + swapchain = dk::SwapchainMaker{device, nwindowGetDefault(), fb_array}.create(); + + // Generate the main rendering cmdlist + recordStaticCommands(); + + // Initialize the projection matrix + transformState.projMtx = glm::perspectiveRH_ZO( + glm::radians(40.0f), + float(framebufferWidth)/float(framebufferHeight), + 0.01f, 1000.0f); + } + + void destroyFramebufferResources() + { + // Return early if we have nothing to destroy + if (!swapchain) return; + + // Make sure the queue is idle before destroying anything + queue.waitIdle(); + + // Clear the static cmdbuf, destroying the static cmdlists in the process + cmdbuf.clear(); + + // Destroy the swapchain + swapchain.destroy(); + + // Destroy the framebuffers + for (unsigned i = 0; i < NumFramebuffers; i ++) + framebuffers_mem[i].destroy(); + + // Destroy the depth buffer + depthBuffer_mem.destroy(); + } + + void recordStaticCommands() + { + // Initialize state structs with deko3d defaults + dk::RasterizerState rasterizerState; + dk::ColorState colorState; + dk::ColorWriteState colorWriteState; + dk::DepthStencilState depthStencilState; + + // Configure viewport and scissor + cmdbuf.setViewports(0, { { 0.0f, 0.0f, (float)framebufferWidth, (float)framebufferHeight, 0.0f, 1.0f } }); + cmdbuf.setScissors(0, { { 0, 0, framebufferWidth, framebufferHeight } }); + + // Clear the color and depth buffers + cmdbuf.clearColor(0, DkColorMask_RGBA, 0.0f, 0.0f, 0.0f, 0.0f); + cmdbuf.clearDepthStencil(true, 1.0f, 0xFF, 0); + + // Bind state required for drawing the cube + cmdbuf.bindShaders(DkStageFlag_GraphicsMask, { vertexShader, fragmentShader }); + cmdbuf.bindUniformBuffer(DkStage_Vertex, 0, transformUniformBuffer.getGpuAddr(), transformUniformBuffer.getSize()); + cmdbuf.bindTextures(DkStage_Fragment, 0, dkMakeTextureHandle(0, 0)); + cmdbuf.bindRasterizerState(rasterizerState); + cmdbuf.bindColorState(colorState); + cmdbuf.bindColorWriteState(colorWriteState); + cmdbuf.bindDepthStencilState(depthStencilState); + cmdbuf.bindVtxBuffer(0, vertexBuffer.getGpuAddr(), vertexBuffer.getSize()); + cmdbuf.bindVtxAttribState(VertexAttribState); + cmdbuf.bindVtxBufferState(VertexBufferState); + + // Draw the cube + cmdbuf.draw(DkPrimitive_Quads, CubeVertexData.size(), 1, 0, 0); + + // Fragment barrier, to make sure we finish previous work before discarding the depth buffer + cmdbuf.barrier(DkBarrier_Fragments, 0); + + // Discard the depth buffer since we don't need it anymore + cmdbuf.discardDepthStencil(); + + // Finish off this command list + render_cmdlist = cmdbuf.finishList(); + } + + void render() + { + // Begin generating the dynamic command list, for commands that need to be sent only this frame specifically + dynmem.begin(dyncmd); + + // Update the uniform buffer with the new transformation state (this data gets inlined in the command list) + dyncmd.pushConstants( + transformUniformBuffer.getGpuAddr(), transformUniformBuffer.getSize(), + 0, sizeof(transformState), &transformState); + + // Finish off the dynamic command list (which also submits it to the queue) + queue.submitCommands(dynmem.end(dyncmd)); + + // Acquire a framebuffer from the swapchain (and wait for it to be available) + int slot = queue.acquireImage(swapchain); + + // Run the command list that attaches said framebuffer to the queue + queue.submitCommands(framebuffer_cmdlists[slot]); + + // Run the main rendering command list + queue.submitCommands(render_cmdlist); + + // Now that we are done rendering, present it to the screen + queue.presentImage(swapchain, slot); + } + + void onOperationMode(AppletOperationMode mode) override + { + // Destroy the framebuffer resources + destroyFramebufferResources(); + + // Choose framebuffer size + chooseFramebufferSize(framebufferWidth, framebufferHeight, mode); + + // Recreate the framebuffers and its associated resources + createFramebufferResources(); + } + + bool onFrame(u64 ns) override + { + hidScanInput(); + u64 kDown = hidKeysDown(CONTROLLER_P1_AUTO); + if (kDown & KEY_PLUS) + return false; + + float time = ns / 1000000000.0; // double precision division; followed by implicit cast to single precision + float tau = glm::two_pi(); + + float period1 = fractf(time/8.0f); + float period2 = fractf(time/4.0f); + + // Generate the model-view matrix for this frame + // Keep in mind that GLM transformation functions multiply to the right, so essentially we have: + // mdlvMtx = Translate * RotateX * RotateY * Scale + // This means that the Scale operation is applied first, then RotateY, and so on. + transformState.mdlvMtx = glm::mat4{1.0f}; + transformState.mdlvMtx = glm::translate(transformState.mdlvMtx, glm::vec3{0.0f, 0.0f, -3.0f}); + transformState.mdlvMtx = glm::rotate(transformState.mdlvMtx, sinf(period2 * tau) * tau / 8.0f, glm::vec3{1.0f, 0.0f, 0.0f}); + transformState.mdlvMtx = glm::rotate(transformState.mdlvMtx, -period1 * tau, glm::vec3{0.0f, 1.0f, 0.0f}); + transformState.mdlvMtx = glm::scale(transformState.mdlvMtx, glm::vec3{0.5f}); + + render(); + return true; + } +}; + +void Example04(void) +{ + CExample04 app; + app.run(); +} diff --git a/graphics/deko3d/deko_examples/source/Example05_Tessellation.cpp b/graphics/deko3d/deko_examples/source/Example05_Tessellation.cpp new file mode 100644 index 0000000..a456436 --- /dev/null +++ b/graphics/deko3d/deko_examples/source/Example05_Tessellation.cpp @@ -0,0 +1,253 @@ +/* +** deko3d Example 05: Simple Tessellation +** This example shows how to use tessellation. +** New concepts in this example: +** - Using tessellation control and evaluation shaders +** - Controlling tessellation parameters +** - Configuring and using line polygon mode +** - Configuring and using built-in edge smoothing +** - Configuring and using blending (needed for obeying alpha generated by edge smoothing) +*/ + +// Sample Framework headers +#include "SampleFramework/CApplication.h" +#include "SampleFramework/CMemPool.h" +#include "SampleFramework/CShader.h" + +// C++ standard library headers +#include +#include + +namespace +{ + struct Vertex + { + float position[3]; + float color[3]; + }; + + constexpr std::array VertexAttribState = + { + DkVtxAttribState{ 0, 0, offsetof(Vertex, position), DkVtxAttribSize_3x32, DkVtxAttribType_Float, 0 }, + DkVtxAttribState{ 0, 0, offsetof(Vertex, color), DkVtxAttribSize_3x32, DkVtxAttribType_Float, 0 }, + }; + + constexpr std::array VertexBufferState = + { + DkVtxBufferState{ sizeof(Vertex), 0 }, + }; + + constexpr std::array TriangleVertexData = + { + Vertex{ { 0.0f, +1.0f, 0.0f }, { 1.0f, 0.0f, 0.0f } }, + Vertex{ { -1.0f, -1.0f, 0.0f }, { 0.0f, 1.0f, 0.0f } }, + Vertex{ { +1.0f, -1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } }, + }; +} + +class CExample05 final : public CApplication +{ + static constexpr unsigned NumFramebuffers = 2; + static constexpr uint32_t FramebufferWidth = 1280; + static constexpr uint32_t FramebufferHeight = 720; + static constexpr unsigned StaticCmdSize = 0x10000; + + dk::UniqueDevice device; + dk::UniqueQueue queue; + + std::optional pool_images; + std::optional pool_code; + std::optional pool_data; + + dk::UniqueCmdBuf cmdbuf; + + CShader vertexShader; + CShader tessCtrlShader; + CShader tessEvalShader; + CShader fragmentShader; + + CMemPool::Handle vertexBuffer; + + CMemPool::Handle framebuffers_mem[NumFramebuffers]; + dk::Image framebuffers[NumFramebuffers]; + DkCmdList framebuffer_cmdlists[NumFramebuffers]; + dk::UniqueSwapchain swapchain; + + DkCmdList render_cmdlist; + +public: + CExample05() + { + // Create the deko3d device + device = dk::DeviceMaker{}.create(); + + // Create the main queue + queue = dk::QueueMaker{device}.setFlags(DkQueueFlags_Graphics).create(); + + // Create the memory pools + pool_images.emplace(device, DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image, 16*1024*1024); + pool_code.emplace(device, DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached | DkMemBlockFlags_Code, 128*1024); + pool_data.emplace(device, DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached, 1*1024*1024); + + // Create the static command buffer and feed it freshly allocated memory + cmdbuf = dk::CmdBufMaker{device}.create(); + CMemPool::Handle cmdmem = pool_data->allocate(StaticCmdSize); + cmdbuf.addMemory(cmdmem.getMemBlock(), cmdmem.getOffset(), cmdmem.getSize()); + + // Load the shaders + vertexShader.load(*pool_code, "romfs:/shaders/basic_vsh.dksh"); + tessCtrlShader.load(*pool_code, "romfs:/shaders/tess_simple_tcsh.dksh"); + tessEvalShader.load(*pool_code, "romfs:/shaders/tess_simple_tesh.dksh"); + fragmentShader.load(*pool_code, "romfs:/shaders/color_fsh.dksh"); + + // Load the vertex buffer + vertexBuffer = pool_data->allocate(sizeof(TriangleVertexData), alignof(Vertex)); + memcpy(vertexBuffer.getCpuAddr(), TriangleVertexData.data(), vertexBuffer.getSize()); + + // Create the framebuffer resources + createFramebufferResources(); + } + + ~CExample05() + { + // Destroy the framebuffer resources + destroyFramebufferResources(); + + // Destroy the vertex buffer (not strictly needed in this case) + vertexBuffer.destroy(); + } + + void createFramebufferResources() + { + // Create layout for the framebuffers + dk::ImageLayout layout_framebuffer; + dk::ImageLayoutMaker{device} + .setFlags(DkImageFlags_UsageRender | DkImageFlags_UsagePresent | DkImageFlags_HwCompression) + .setFormat(DkImageFormat_RGBA8_Unorm) + .setDimensions(FramebufferWidth, FramebufferHeight) + .initialize(layout_framebuffer); + + // Create the framebuffers + std::array fb_array; + uint64_t fb_size = layout_framebuffer.getSize(); + uint32_t fb_align = layout_framebuffer.getAlignment(); + for (unsigned i = 0; i < NumFramebuffers; i ++) + { + // Allocate a framebuffer + framebuffers_mem[i] = pool_images->allocate(fb_size, fb_align); + framebuffers[i].initialize(layout_framebuffer, framebuffers_mem[i].getMemBlock(), framebuffers_mem[i].getOffset()); + + // Generate a command list that binds it + dk::ImageView colorTarget{ framebuffers[i] }; + cmdbuf.bindRenderTargets(&colorTarget); + framebuffer_cmdlists[i] = cmdbuf.finishList(); + + // Fill in the array for use later by the swapchain creation code + fb_array[i] = &framebuffers[i]; + } + + // Create the swapchain using the framebuffers + swapchain = dk::SwapchainMaker{device, nwindowGetDefault(), fb_array}.create(); + + // Generate the main rendering cmdlist + recordStaticCommands(); + } + + void destroyFramebufferResources() + { + // Return early if we have nothing to destroy + if (!swapchain) return; + + // Make sure the queue is idle before destroying anything + queue.waitIdle(); + + // Clear the static cmdbuf, destroying the static cmdlists in the process + cmdbuf.clear(); + + // Destroy the swapchain + swapchain.destroy(); + + // Destroy the framebuffers + for (unsigned i = 0; i < NumFramebuffers; i ++) + framebuffers_mem[i].destroy(); + } + + void recordStaticCommands() + { + // Initialize state structs with deko3d defaults + dk::RasterizerState rasterizerState; + dk::ColorState colorState; + dk::ColorWriteState colorWriteState; + dk::BlendState blendState; + + // Configure rasterizer state: draw polygons as lines, and enable polygon smoothing + rasterizerState.setPolygonMode(DkPolygonMode_Line); + rasterizerState.setPolygonSmoothEnable(true); + + // Configure color state: enable blending (needed for polygon smoothing since it generates alpha values) + colorState.setBlendEnable(0, true); + + // Configure viewport and scissor + cmdbuf.setViewports(0, { { 0.0f, 0.0f, FramebufferWidth, FramebufferHeight, 0.0f, 1.0f } }); + cmdbuf.setScissors(0, { { 0, 0, FramebufferWidth, FramebufferHeight } }); + + // Clear the color buffer + cmdbuf.clearColor(0, DkColorMask_RGBA, 0.0f, 0.0f, 0.0f, 0.0f); + + // Bind state required for drawing the triangle + cmdbuf.bindShaders(DkStageFlag_GraphicsMask, { vertexShader, tessCtrlShader, tessEvalShader, fragmentShader }); + cmdbuf.bindRasterizerState(rasterizerState); + cmdbuf.bindColorState(colorState); + cmdbuf.bindColorWriteState(colorWriteState); + cmdbuf.bindBlendStates(0, blendState); + cmdbuf.bindVtxBuffer(0, vertexBuffer.getGpuAddr(), vertexBuffer.getSize()); + cmdbuf.bindVtxAttribState(VertexAttribState); + cmdbuf.bindVtxBufferState(VertexBufferState); + cmdbuf.setLineWidth(4.0f); + cmdbuf.setPatchSize(3); + + // Note that the tessellation control shader is optional. If no such shader is bound, + // the following commands can be used to control tessellation: + // (try it out! remove the "tessCtrlShader" from the bindShaders call and uncomment these) + //cmdbuf.setTessInnerLevels(5.0f); + //cmdbuf.setTessOuterLevels(7.0f, 3.0f, 5.0f); + + // Draw the triangle + cmdbuf.draw(DkPrimitive_Patches, TriangleVertexData.size(), 1, 0, 0); + + // Finish off this command list + render_cmdlist = cmdbuf.finishList(); + } + + void render() + { + // Acquire a framebuffer from the swapchain (and wait for it to be available) + int slot = queue.acquireImage(swapchain); + + // Run the command list that attaches said framebuffer to the queue + queue.submitCommands(framebuffer_cmdlists[slot]); + + // Run the main rendering command list + queue.submitCommands(render_cmdlist); + + // Now that we are done rendering, present it to the screen + queue.presentImage(swapchain, slot); + } + + bool onFrame(u64 ns) override + { + hidScanInput(); + u64 kDown = hidKeysDown(CONTROLLER_P1_AUTO); + if (kDown & KEY_PLUS) + return false; + + render(); + return true; + } +}; + +void Example05(void) +{ + CExample05 app; + app.run(); +} diff --git a/graphics/deko3d/deko_examples/source/Example06_Multisampling.cpp b/graphics/deko3d/deko_examples/source/Example06_Multisampling.cpp new file mode 100644 index 0000000..639dddd --- /dev/null +++ b/graphics/deko3d/deko_examples/source/Example06_Multisampling.cpp @@ -0,0 +1,408 @@ +/* +** deko3d Example 06: Simple Multisampling +** This example shows how to use a multisampled render target, which is then resolved into the final framebuffer. +** New concepts in this example: +** - Creating multisampled render targets +** - Rendering to non-swapchain render targets +** - Configuring multisample state +** - Performing a resolve step +** - Discarding color/depth buffers that are not used for presentation +*/ + +// Sample Framework headers +#include "SampleFramework/CApplication.h" +#include "SampleFramework/CMemPool.h" +#include "SampleFramework/CShader.h" +#include "SampleFramework/CCmdMemRing.h" + +// C++ standard library headers +#include +#include + +// GLM headers +#define GLM_FORCE_DEFAULT_ALIGNED_GENTYPES // Enforces GLSL std140/std430 alignment rules for glm types +#define GLM_FORCE_INTRINSICS // Enables usage of SIMD CPU instructions (requiring the above as well) +#include +#include +#include +#include + +namespace +{ + struct Vertex + { + float position[3]; + float color[3]; + }; + + constexpr std::array VertexAttribState = + { + DkVtxAttribState{ 0, 0, offsetof(Vertex, position), DkVtxAttribSize_3x32, DkVtxAttribType_Float, 0 }, + DkVtxAttribState{ 0, 0, offsetof(Vertex, color), DkVtxAttribSize_3x32, DkVtxAttribType_Float, 0 }, + }; + + constexpr std::array VertexBufferState = + { + DkVtxBufferState{ sizeof(Vertex), 0 }, + }; + + constexpr std::array CubeVertexData = + { + // +X face + Vertex{ { +1.0f, +1.0f, +1.0f }, { 1.0f, 0.0f, 0.0f } }, + Vertex{ { +1.0f, -1.0f, +1.0f }, { 0.0f, 1.0f, 0.0f } }, + Vertex{ { +1.0f, -1.0f, -1.0f }, { 0.0f, 0.0f, 1.0f } }, + Vertex{ { +1.0f, +1.0f, -1.0f }, { 1.0f, 1.0f, 0.0f } }, + + // -X face + Vertex{ { -1.0f, +1.0f, -1.0f }, { 1.0f, 0.0f, 0.0f } }, + Vertex{ { -1.0f, -1.0f, -1.0f }, { 0.0f, 1.0f, 0.0f } }, + Vertex{ { -1.0f, -1.0f, +1.0f }, { 0.0f, 0.0f, 1.0f } }, + Vertex{ { -1.0f, +1.0f, +1.0f }, { 1.0f, 1.0f, 0.0f } }, + + // +Y face + Vertex{ { -1.0f, +1.0f, -1.0f }, { 1.0f, 0.0f, 0.0f } }, + Vertex{ { -1.0f, +1.0f, +1.0f }, { 0.0f, 1.0f, 0.0f } }, + Vertex{ { +1.0f, +1.0f, +1.0f }, { 0.0f, 0.0f, 1.0f } }, + Vertex{ { +1.0f, +1.0f, -1.0f }, { 1.0f, 1.0f, 0.0f } }, + + // -Y face + Vertex{ { -1.0f, -1.0f, +1.0f }, { 1.0f, 0.0f, 0.0f } }, + Vertex{ { -1.0f, -1.0f, -1.0f }, { 0.0f, 1.0f, 0.0f } }, + Vertex{ { +1.0f, -1.0f, -1.0f }, { 0.0f, 0.0f, 1.0f } }, + Vertex{ { +1.0f, -1.0f, +1.0f }, { 1.0f, 1.0f, 0.0f } }, + + // +Z face + Vertex{ { -1.0f, +1.0f, +1.0f }, { 1.0f, 0.0f, 0.0f } }, + Vertex{ { -1.0f, -1.0f, +1.0f }, { 0.0f, 1.0f, 0.0f } }, + Vertex{ { +1.0f, -1.0f, +1.0f }, { 0.0f, 0.0f, 1.0f } }, + Vertex{ { +1.0f, +1.0f, +1.0f }, { 1.0f, 1.0f, 0.0f } }, + + // -Z face + Vertex{ { +1.0f, +1.0f, -1.0f }, { 1.0f, 0.0f, 0.0f } }, + Vertex{ { +1.0f, -1.0f, -1.0f }, { 0.0f, 1.0f, 0.0f } }, + Vertex{ { -1.0f, -1.0f, -1.0f }, { 0.0f, 0.0f, 1.0f } }, + Vertex{ { -1.0f, +1.0f, -1.0f }, { 1.0f, 1.0f, 0.0f } }, + }; + + struct Transformation + { + glm::mat4 mdlvMtx; + glm::mat4 projMtx; + }; + + inline float fractf(float x) + { + return x - floorf(x); + } +} + +class CExample06 final : public CApplication +{ + static constexpr unsigned NumFramebuffers = 2; + static constexpr unsigned StaticCmdSize = 0x10000; + static constexpr unsigned DynamicCmdSize = 0x10000; + static constexpr DkMsMode MultisampleMode = DkMsMode_4x; + + dk::UniqueDevice device; + dk::UniqueQueue queue; + + std::optional pool_images; + std::optional pool_code; + std::optional pool_data; + + dk::UniqueCmdBuf cmdbuf; + dk::UniqueCmdBuf dyncmd; + CCmdMemRing dynmem; + + CShader vertexShader; + CShader fragmentShader; + + Transformation transformState; + CMemPool::Handle transformUniformBuffer; + + CMemPool::Handle vertexBuffer; + + uint32_t framebufferWidth; + uint32_t framebufferHeight; + + CMemPool::Handle colorBuffer_mem; + CMemPool::Handle depthBuffer_mem; + CMemPool::Handle framebuffers_mem[NumFramebuffers]; + + dk::Image colorBuffer; + dk::Image depthBuffer; + dk::Image framebuffers[NumFramebuffers]; + DkCmdList framebuffer_cmdlists[NumFramebuffers]; + dk::UniqueSwapchain swapchain; + + DkCmdList render_cmdlist, discard_cmdlist; + +public: + CExample06() + { + // Create the deko3d device + device = dk::DeviceMaker{}.create(); + + // Create the main queue + queue = dk::QueueMaker{device}.setFlags(DkQueueFlags_Graphics).create(); + + // Create the memory pools + pool_images.emplace(device, DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image, 64*1024*1024); + pool_code.emplace(device, DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached | DkMemBlockFlags_Code, 128*1024); + pool_data.emplace(device, DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached, 1*1024*1024); + + // Create the static command buffer and feed it freshly allocated memory + cmdbuf = dk::CmdBufMaker{device}.create(); + CMemPool::Handle cmdmem = pool_data->allocate(StaticCmdSize); + cmdbuf.addMemory(cmdmem.getMemBlock(), cmdmem.getOffset(), cmdmem.getSize()); + + // Create the dynamic command buffer and allocate memory for it + dyncmd = dk::CmdBufMaker{device}.create(); + dynmem.allocate(*pool_data, DynamicCmdSize); + + // Load the shaders + vertexShader.load(*pool_code, "romfs:/shaders/transform_vsh.dksh"); + fragmentShader.load(*pool_code, "romfs:/shaders/color_fsh.dksh"); + + // Create the transformation uniform buffer + transformUniformBuffer = pool_data->allocate(sizeof(transformState), DK_UNIFORM_BUF_ALIGNMENT); + + // Load the vertex buffer + vertexBuffer = pool_data->allocate(sizeof(CubeVertexData), alignof(Vertex)); + memcpy(vertexBuffer.getCpuAddr(), CubeVertexData.data(), vertexBuffer.getSize()); + } + + ~CExample06() + { + // Destroy the framebuffer resources + destroyFramebufferResources(); + + // Destroy the vertex buffer (not strictly needed in this case) + vertexBuffer.destroy(); + + // Destroy the uniform buffer (not strictly needed in this case) + transformUniformBuffer.destroy(); + } + + void createFramebufferResources() + { + // Create layout for the (multisampled) color buffer + dk::ImageLayout layout_colorbuffer; + dk::ImageLayoutMaker{device} + .setType(DkImageType_2DMS) + .setFlags(DkImageFlags_UsageRender | DkImageFlags_Usage2DEngine | DkImageFlags_HwCompression) + .setFormat(DkImageFormat_RGBA8_Unorm) + .setMsMode(MultisampleMode) + .setDimensions(framebufferWidth, framebufferHeight) + .initialize(layout_colorbuffer); + + // Create layout for the (also multisampled) depth buffer + dk::ImageLayout layout_depthbuffer; + dk::ImageLayoutMaker{device} + .setType(DkImageType_2DMS) + .setFlags(DkImageFlags_UsageRender | DkImageFlags_HwCompression) + .setFormat(DkImageFormat_Z24S8) + .setMsMode(MultisampleMode) + .setDimensions(framebufferWidth, framebufferHeight) + .initialize(layout_depthbuffer); + + // Create the color buffer + colorBuffer_mem = pool_images->allocate(layout_colorbuffer.getSize(), layout_colorbuffer.getAlignment()); + colorBuffer.initialize(layout_colorbuffer, colorBuffer_mem.getMemBlock(), colorBuffer_mem.getOffset()); + + // Create the depth buffer + depthBuffer_mem = pool_images->allocate(layout_depthbuffer.getSize(), layout_depthbuffer.getAlignment()); + depthBuffer.initialize(layout_depthbuffer, depthBuffer_mem.getMemBlock(), depthBuffer_mem.getOffset()); + + // Create layout for the framebuffers + dk::ImageLayout layout_framebuffer; + dk::ImageLayoutMaker{device} + .setFlags(DkImageFlags_Usage2DEngine | DkImageFlags_UsagePresent) + .setFormat(DkImageFormat_RGBA8_Unorm) + .setDimensions(framebufferWidth, framebufferHeight) + .initialize(layout_framebuffer); + + // Create the framebuffers + std::array fb_array; + uint64_t fb_size = layout_framebuffer.getSize(); + uint32_t fb_align = layout_framebuffer.getAlignment(); + for (unsigned i = 0; i < NumFramebuffers; i ++) + { + // Allocate a framebuffer + framebuffers_mem[i] = pool_images->allocate(fb_size, fb_align); + framebuffers[i].initialize(layout_framebuffer, framebuffers_mem[i].getMemBlock(), framebuffers_mem[i].getOffset()); + + // Generate a command list that resolves the color buffer into the framebuffer + dk::ImageView colorView { colorBuffer }, framebufferView { framebuffers[i] }; + cmdbuf.resolveImage(colorView, framebufferView); + framebuffer_cmdlists[i] = cmdbuf.finishList(); + + // Fill in the array for use later by the swapchain creation code + fb_array[i] = &framebuffers[i]; + } + + // Create the swapchain using the framebuffers + swapchain = dk::SwapchainMaker{device, nwindowGetDefault(), fb_array}.create(); + + // Generate the main command lists + recordStaticCommands(); + + // Initialize the projection matrix + transformState.projMtx = glm::perspectiveRH_ZO( + glm::radians(40.0f), + float(framebufferWidth)/float(framebufferHeight), + 0.01f, 1000.0f); + } + + void destroyFramebufferResources() + { + // Return early if we have nothing to destroy + if (!swapchain) return; + + // Make sure the queue is idle before destroying anything + queue.waitIdle(); + + // Clear the static cmdbuf, destroying the static cmdlists in the process + cmdbuf.clear(); + + // Destroy the swapchain + swapchain.destroy(); + + // Destroy the framebuffers + for (unsigned i = 0; i < NumFramebuffers; i ++) + framebuffers_mem[i].destroy(); + + // Destroy the depth buffer + depthBuffer_mem.destroy(); + + // Destroy the color buffer + colorBuffer_mem.destroy(); + } + + void recordStaticCommands() + { + // Initialize state structs with deko3d defaults + dk::RasterizerState rasterizerState; + dk::MultisampleState multisampleState; + dk::ColorState colorState; + dk::ColorWriteState colorWriteState; + dk::DepthStencilState depthStencilState; + + // Configure multisample state + multisampleState.setMode(MultisampleMode); + multisampleState.setLocations(); + + // Bind color buffer and depth buffer + dk::ImageView colorTarget { colorBuffer }, depthTarget { depthBuffer }; + cmdbuf.bindRenderTargets(&colorTarget, &depthTarget); + + // Configure viewport and scissor + cmdbuf.setViewports(0, { { 0.0f, 0.0f, (float)framebufferWidth, (float)framebufferHeight, 0.0f, 1.0f } }); + cmdbuf.setScissors(0, { { 0, 0, framebufferWidth, framebufferHeight } }); + + // Clear the color and depth buffers + cmdbuf.clearColor(0, DkColorMask_RGBA, 0.0f, 0.0f, 0.0f, 0.0f); + cmdbuf.clearDepthStencil(true, 1.0f, 0xFF, 0); + + // Bind state required for drawing the cube + cmdbuf.bindShaders(DkStageFlag_GraphicsMask, { vertexShader, fragmentShader }); + cmdbuf.bindUniformBuffer(DkStage_Vertex, 0, transformUniformBuffer.getGpuAddr(), transformUniformBuffer.getSize()); + cmdbuf.bindRasterizerState(rasterizerState); + cmdbuf.bindMultisampleState(multisampleState); + cmdbuf.bindColorState(colorState); + cmdbuf.bindColorWriteState(colorWriteState); + cmdbuf.bindDepthStencilState(depthStencilState); + cmdbuf.bindVtxBuffer(0, vertexBuffer.getGpuAddr(), vertexBuffer.getSize()); + cmdbuf.bindVtxAttribState(VertexAttribState); + cmdbuf.bindVtxBufferState(VertexBufferState); + + // Draw the cube + cmdbuf.draw(DkPrimitive_Quads, CubeVertexData.size(), 1, 0, 0); + + // Finish off this command list + render_cmdlist = cmdbuf.finishList(); + + // Discard the color and depth buffers since we don't need them anymore + cmdbuf.bindRenderTargets(&colorTarget, &depthTarget); + cmdbuf.discardColor(0); + cmdbuf.discardDepthStencil(); + + // Finish off this command list + discard_cmdlist = cmdbuf.finishList(); + } + + void render() + { + // Begin generating the dynamic command list, for commands that need to be sent only this frame specifically + dynmem.begin(dyncmd); + + // Update the uniform buffer with the new transformation state (this data gets inlined in the command list) + dyncmd.pushConstants( + transformUniformBuffer.getGpuAddr(), transformUniformBuffer.getSize(), + 0, sizeof(transformState), &transformState); + + // Finish off the dynamic command list (which also submits it to the queue) + queue.submitCommands(dynmem.end(dyncmd)); + + // Run the main rendering command list + queue.submitCommands(render_cmdlist); + + // Acquire a framebuffer from the swapchain + int slot = queue.acquireImage(swapchain); + + // Submit the command list that resolves the color buffer to the framebuffer + queue.submitCommands(framebuffer_cmdlists[slot]); + + // Submit the command list used for discarding the color and depth buffers + queue.submitCommands(discard_cmdlist); + + // Now that we are done rendering, present it to the screen (this also flushes the queue) + queue.presentImage(swapchain, slot); + } + + void onOperationMode(AppletOperationMode mode) override + { + // Destroy the framebuffer resources + destroyFramebufferResources(); + + // Choose framebuffer size + chooseFramebufferSize(framebufferWidth, framebufferHeight, mode); + + // Recreate the framebuffers and its associated resources + createFramebufferResources(); + } + + bool onFrame(u64 ns) override + { + hidScanInput(); + u64 kDown = hidKeysDown(CONTROLLER_P1_AUTO); + if (kDown & KEY_PLUS) + return false; + + float time = ns / 1000000000.0; // double precision division; followed by implicit cast to single precision + float tau = glm::two_pi(); + + float period1 = fractf(time/8.0f); + float period2 = fractf(time/4.0f); + + // Generate the model-view matrix for this frame + // Keep in mind that GLM transformation functions multiply to the right, so essentially we have: + // mdlvMtx = Translate * RotateX * RotateY * Scale + // This means that the Scale operation is applied first, then RotateY, and so on. + transformState.mdlvMtx = glm::mat4{1.0f}; + transformState.mdlvMtx = glm::translate(transformState.mdlvMtx, glm::vec3{0.0f, 0.0f, -3.0f}); + transformState.mdlvMtx = glm::rotate(transformState.mdlvMtx, sinf(period2 * tau) * tau / 8.0f, glm::vec3{1.0f, 0.0f, 0.0f}); + transformState.mdlvMtx = glm::rotate(transformState.mdlvMtx, -period1 * tau, glm::vec3{0.0f, 1.0f, 0.0f}); + transformState.mdlvMtx = glm::scale(transformState.mdlvMtx, glm::vec3{0.5f}); + + render(); + return true; + } +}; + +void Example06(void) +{ + CExample06 app; + app.run(); +} diff --git a/graphics/deko3d/deko_examples/source/Example07_MeshLighting.cpp b/graphics/deko3d/deko_examples/source/Example07_MeshLighting.cpp new file mode 100644 index 0000000..9c3089d --- /dev/null +++ b/graphics/deko3d/deko_examples/source/Example07_MeshLighting.cpp @@ -0,0 +1,401 @@ +/* +** deko3d Example 07: Mesh Loading and Lighting (sRGB) +** This example shows how to load a mesh, and render it using per-fragment lighting. +** New concepts in this example: +** - Loading geometry data (mesh) from the filesystem +** - Configuring and using index buffers +** - Using sRGB framebuffers +** - Using multiple uniform buffers on different stages +** - Basic Blinn-Phong lighting with Reinhard tone mapping +*/ + +// Sample Framework headers +#include "SampleFramework/CApplication.h" +#include "SampleFramework/CMemPool.h" +#include "SampleFramework/CShader.h" +#include "SampleFramework/CCmdMemRing.h" +#include "SampleFramework/FileLoader.h" + +// C++ standard library headers +#include +#include + +// GLM headers +#define GLM_FORCE_DEFAULT_ALIGNED_GENTYPES // Enforces GLSL std140/std430 alignment rules for glm types +#define GLM_FORCE_INTRINSICS // Enables usage of SIMD CPU instructions (requiring the above as well) +#include +#include +#include +#include + +namespace +{ + struct Vertex + { + float position[3]; + float normal[3]; + }; + + constexpr std::array VertexAttribState = + { + DkVtxAttribState{ 0, 0, offsetof(Vertex, position), DkVtxAttribSize_3x32, DkVtxAttribType_Float, 0 }, + DkVtxAttribState{ 0, 0, offsetof(Vertex, normal), DkVtxAttribSize_3x32, DkVtxAttribType_Float, 0 }, + }; + + constexpr std::array VertexBufferState = + { + DkVtxBufferState{ sizeof(Vertex), 0 }, + }; + + struct Transformation + { + glm::mat4 mdlvMtx; + glm::mat4 projMtx; + }; + + struct Lighting + { + glm::vec4 lightPos; // if w=0 this is lightDir + glm::vec3 ambient; + glm::vec3 diffuse; + glm::vec4 specular; // w is shininess + }; + + inline float fractf(float x) + { + return x - floorf(x); + } +} + +class CExample07 final : public CApplication +{ + static constexpr unsigned NumFramebuffers = 2; + static constexpr unsigned StaticCmdSize = 0x10000; + static constexpr unsigned DynamicCmdSize = 0x10000; + static constexpr DkMsMode MultisampleMode = DkMsMode_4x; + + dk::UniqueDevice device; + dk::UniqueQueue queue; + + std::optional pool_images; + std::optional pool_code; + std::optional pool_data; + + dk::UniqueCmdBuf cmdbuf; + dk::UniqueCmdBuf dyncmd; + CCmdMemRing dynmem; + + CShader vertexShader; + CShader fragmentShader; + + Transformation transformState; + CMemPool::Handle transformUniformBuffer; + + Lighting lightingState; + CMemPool::Handle lightingUniformBuffer; + + CMemPool::Handle vertexBuffer; + CMemPool::Handle indexBuffer; + + uint32_t framebufferWidth; + uint32_t framebufferHeight; + + CMemPool::Handle colorBuffer_mem; + CMemPool::Handle depthBuffer_mem; + CMemPool::Handle framebuffers_mem[NumFramebuffers]; + + dk::Image colorBuffer; + dk::Image depthBuffer; + dk::Image framebuffers[NumFramebuffers]; + DkCmdList framebuffer_cmdlists[NumFramebuffers]; + dk::UniqueSwapchain swapchain; + + DkCmdList render_cmdlist, discard_cmdlist; + +public: + CExample07() + { + // Create the deko3d device + device = dk::DeviceMaker{}.create(); + + // Create the main queue + queue = dk::QueueMaker{device}.setFlags(DkQueueFlags_Graphics).create(); + + // Create the memory pools + pool_images.emplace(device, DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image, 64*1024*1024); + pool_code.emplace(device, DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached | DkMemBlockFlags_Code, 128*1024); + pool_data.emplace(device, DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached, 1*1024*1024); + + // Create the static command buffer and feed it freshly allocated memory + cmdbuf = dk::CmdBufMaker{device}.create(); + CMemPool::Handle cmdmem = pool_data->allocate(StaticCmdSize); + cmdbuf.addMemory(cmdmem.getMemBlock(), cmdmem.getOffset(), cmdmem.getSize()); + + // Create the dynamic command buffer and allocate memory for it + dyncmd = dk::CmdBufMaker{device}.create(); + dynmem.allocate(*pool_data, DynamicCmdSize); + + // Load the shaders + vertexShader.load(*pool_code, "romfs:/shaders/transform_normal_vsh.dksh"); + fragmentShader.load(*pool_code, "romfs:/shaders/basic_lighting_fsh.dksh"); + + // Create the transformation uniform buffer + transformUniformBuffer = pool_data->allocate(sizeof(transformState), DK_UNIFORM_BUF_ALIGNMENT); + + // Create the lighting uniform buffer + lightingUniformBuffer = pool_data->allocate(sizeof(lightingState), DK_UNIFORM_BUF_ALIGNMENT); + + // Initialize the lighting state + lightingState.lightPos = glm::vec4{0.0f, 4.0f, 1.0f, 1.0f}; + lightingState.ambient = glm::vec3{0.046227f,0.028832f,0.003302f}; + lightingState.diffuse = glm::vec3{0.564963f,0.367818f,0.051293f}; + lightingState.specular = glm::vec4{24.0f*glm::vec3{0.394737f,0.308916f,0.134004f}, 64.0f}; + + // Load the teapot mesh + vertexBuffer = LoadFile(*pool_data, "romfs:/teapot-vtx.bin", alignof(Vertex)); + indexBuffer = LoadFile(*pool_data, "romfs:/teapot-idx.bin", alignof(u16)); + } + + ~CExample07() + { + // Destroy the framebuffer resources + destroyFramebufferResources(); + + // Destroy the index buffer (not strictly needed in this case) + indexBuffer.destroy(); + + // Destroy the vertex buffer (not strictly needed in this case) + vertexBuffer.destroy(); + + // Destroy the uniform buffer (not strictly needed in this case) + transformUniformBuffer.destroy(); + } + + void createFramebufferResources() + { + // Create layout for the (multisampled) color buffer + dk::ImageLayout layout_colorbuffer; + dk::ImageLayoutMaker{device} + .setType(DkImageType_2DMS) + .setFlags(DkImageFlags_UsageRender | DkImageFlags_Usage2DEngine | DkImageFlags_HwCompression) + .setFormat(DkImageFormat_RGBA8_Unorm_sRGB) + .setMsMode(MultisampleMode) + .setDimensions(framebufferWidth, framebufferHeight) + .initialize(layout_colorbuffer); + + // Create layout for the (also multisampled) depth buffer + dk::ImageLayout layout_depthbuffer; + dk::ImageLayoutMaker{device} + .setType(DkImageType_2DMS) + .setFlags(DkImageFlags_UsageRender | DkImageFlags_HwCompression) + .setFormat(DkImageFormat_Z24S8) + .setMsMode(MultisampleMode) + .setDimensions(framebufferWidth, framebufferHeight) + .initialize(layout_depthbuffer); + + // Create the color buffer + colorBuffer_mem = pool_images->allocate(layout_colorbuffer.getSize(), layout_colorbuffer.getAlignment()); + colorBuffer.initialize(layout_colorbuffer, colorBuffer_mem.getMemBlock(), colorBuffer_mem.getOffset()); + + // Create the depth buffer + depthBuffer_mem = pool_images->allocate(layout_depthbuffer.getSize(), layout_depthbuffer.getAlignment()); + depthBuffer.initialize(layout_depthbuffer, depthBuffer_mem.getMemBlock(), depthBuffer_mem.getOffset()); + + // Create layout for the framebuffers + dk::ImageLayout layout_framebuffer; + dk::ImageLayoutMaker{device} + .setFlags(DkImageFlags_Usage2DEngine | DkImageFlags_UsagePresent) + .setFormat(DkImageFormat_RGBA8_Unorm_sRGB) + .setDimensions(framebufferWidth, framebufferHeight) + .initialize(layout_framebuffer); + + // Create the framebuffers + std::array fb_array; + uint64_t fb_size = layout_framebuffer.getSize(); + uint32_t fb_align = layout_framebuffer.getAlignment(); + for (unsigned i = 0; i < NumFramebuffers; i ++) + { + // Allocate a framebuffer + framebuffers_mem[i] = pool_images->allocate(fb_size, fb_align); + framebuffers[i].initialize(layout_framebuffer, framebuffers_mem[i].getMemBlock(), framebuffers_mem[i].getOffset()); + + // Generate a command list that resolves the color buffer into the framebuffer + dk::ImageView colorView { colorBuffer }, framebufferView { framebuffers[i] }; + cmdbuf.resolveImage(colorView, framebufferView); + framebuffer_cmdlists[i] = cmdbuf.finishList(); + + // Fill in the array for use later by the swapchain creation code + fb_array[i] = &framebuffers[i]; + } + + // Create the swapchain using the framebuffers + swapchain = dk::SwapchainMaker{device, nwindowGetDefault(), fb_array}.create(); + + // Generate the main command lists + recordStaticCommands(); + + // Initialize the projection matrix + transformState.projMtx = glm::perspectiveRH_ZO( + glm::radians(40.0f), + float(framebufferWidth)/float(framebufferHeight), + 0.01f, 1000.0f); + } + + void destroyFramebufferResources() + { + // Return early if we have nothing to destroy + if (!swapchain) return; + + // Make sure the queue is idle before destroying anything + queue.waitIdle(); + + // Clear the static cmdbuf, destroying the static cmdlists in the process + cmdbuf.clear(); + + // Destroy the swapchain + swapchain.destroy(); + + // Destroy the framebuffers + for (unsigned i = 0; i < NumFramebuffers; i ++) + framebuffers_mem[i].destroy(); + + // Destroy the depth buffer + depthBuffer_mem.destroy(); + + // Destroy the color buffer + colorBuffer_mem.destroy(); + } + + void recordStaticCommands() + { + // Initialize state structs with deko3d defaults + dk::RasterizerState rasterizerState; + dk::MultisampleState multisampleState; + dk::ColorState colorState; + dk::ColorWriteState colorWriteState; + dk::DepthStencilState depthStencilState; + + // Configure multisample state + multisampleState.setMode(MultisampleMode); + multisampleState.setLocations(); + + // Bind color buffer and depth buffer + dk::ImageView colorTarget { colorBuffer }, depthTarget { depthBuffer }; + cmdbuf.bindRenderTargets(&colorTarget, &depthTarget); + + // Configure viewport and scissor + cmdbuf.setViewports(0, { { 0.0f, 0.0f, (float)framebufferWidth, (float)framebufferHeight, 0.0f, 1.0f } }); + cmdbuf.setScissors(0, { { 0, 0, framebufferWidth, framebufferHeight } }); + + // Clear the color and depth buffers + cmdbuf.clearColor(0, DkColorMask_RGBA, 0.0f, 0.0f, 0.0f, 0.0f); + cmdbuf.clearDepthStencil(true, 1.0f, 0xFF, 0); + + // Bind state required for drawing the mesh + cmdbuf.bindShaders(DkStageFlag_GraphicsMask, { vertexShader, fragmentShader }); + cmdbuf.bindUniformBuffer(DkStage_Vertex, 0, transformUniformBuffer.getGpuAddr(), transformUniformBuffer.getSize()); + cmdbuf.bindUniformBuffer(DkStage_Fragment, 0, lightingUniformBuffer.getGpuAddr(), lightingUniformBuffer.getSize()); + cmdbuf.bindRasterizerState(rasterizerState); + cmdbuf.bindMultisampleState(multisampleState); + cmdbuf.bindColorState(colorState); + cmdbuf.bindColorWriteState(colorWriteState); + cmdbuf.bindDepthStencilState(depthStencilState); + cmdbuf.bindVtxBuffer(0, vertexBuffer.getGpuAddr(), vertexBuffer.getSize()); + cmdbuf.bindVtxAttribState(VertexAttribState); + cmdbuf.bindVtxBufferState(VertexBufferState); + cmdbuf.bindIdxBuffer(DkIdxFormat_Uint16, indexBuffer.getGpuAddr()); + + // Draw the mesh + cmdbuf.drawIndexed(DkPrimitive_Triangles, indexBuffer.getSize() / sizeof(u16), 1, 0, 0, 0); + + // Finish off this command list + render_cmdlist = cmdbuf.finishList(); + + // Discard the color and depth buffers since we don't need them anymore + cmdbuf.bindRenderTargets(&colorTarget, &depthTarget); + cmdbuf.discardColor(0); + cmdbuf.discardDepthStencil(); + + // Finish off this command list + discard_cmdlist = cmdbuf.finishList(); + } + + void render() + { + // Begin generating the dynamic command list, for commands that need to be sent only this frame specifically + dynmem.begin(dyncmd); + + // Update the transformation uniform buffer with the new state (this data gets inlined in the command list) + dyncmd.pushConstants( + transformUniformBuffer.getGpuAddr(), transformUniformBuffer.getSize(), + 0, sizeof(transformState), &transformState); + + // Update the lighting uniform buffer with the new state + dyncmd.pushConstants( + lightingUniformBuffer.getGpuAddr(), lightingUniformBuffer.getSize(), + 0, sizeof(lightingState), &lightingState); + + // Finish off the dynamic command list (which also submits it to the queue) + queue.submitCommands(dynmem.end(dyncmd)); + + // Run the main rendering command list + queue.submitCommands(render_cmdlist); + + // Acquire a framebuffer from the swapchain + int slot = queue.acquireImage(swapchain); + + // Submit the command list that resolves the color buffer to the framebuffer + queue.submitCommands(framebuffer_cmdlists[slot]); + + // Submit the command list used for discarding the color and depth buffers + queue.submitCommands(discard_cmdlist); + + // Now that we are done rendering, present it to the screen (this also flushes the queue) + queue.presentImage(swapchain, slot); + } + + void onOperationMode(AppletOperationMode mode) override + { + // Destroy the framebuffer resources + destroyFramebufferResources(); + + // Choose framebuffer size + chooseFramebufferSize(framebufferWidth, framebufferHeight, mode); + + // Recreate the framebuffers and its associated resources + createFramebufferResources(); + } + + bool onFrame(u64 ns) override + { + hidScanInput(); + u64 kDown = hidKeysDown(CONTROLLER_P1_AUTO); + if (kDown & KEY_PLUS) + return false; + + float time = ns / 1000000000.0; // double precision division; followed by implicit cast to single precision + float tau = glm::two_pi(); + + float period1 = fractf(time/8.0f); + float period2 = fractf(time/4.0f); + + // Generate the model-view matrix for this frame + // Keep in mind that GLM transformation functions multiply to the right, so essentially we have: + // mdlvMtx = Translate1 * RotateX * RotateY * Translate2 + // This means that the Translate2 operation is applied first, then RotateY, and so on. + transformState.mdlvMtx = glm::mat4{1.0f}; + transformState.mdlvMtx = glm::translate(transformState.mdlvMtx, glm::vec3{0.0f, 0.0f, -3.0f}); + transformState.mdlvMtx = glm::rotate(transformState.mdlvMtx, sinf(period2 * tau) * tau / 8.0f, glm::vec3{1.0f, 0.0f, 0.0f}); + transformState.mdlvMtx = glm::rotate(transformState.mdlvMtx, -period1 * tau, glm::vec3{0.0f, 1.0f, 0.0f}); + transformState.mdlvMtx = glm::translate(transformState.mdlvMtx, glm::vec3{0.0f, -0.5f, 0.0f}); + + render(); + return true; + } +}; + +void Example07(void) +{ + CExample07 app; + app.run(); +} diff --git a/graphics/deko3d/deko_examples/source/Example08_DeferredShading.cpp b/graphics/deko3d/deko_examples/source/Example08_DeferredShading.cpp new file mode 100644 index 0000000..7a24662 --- /dev/null +++ b/graphics/deko3d/deko_examples/source/Example08_DeferredShading.cpp @@ -0,0 +1,482 @@ +/* +** deko3d Example 08: Deferred Shading (Multipass Rendering with Tiled Cache) +** This example shows how to perform deferred shading, a multipass rendering technique that goes well with the tiled cache. +** New concepts in this example: +** - Rendering to multiple render targets (MRT) at once +** - Floating point render targets +** - Enabling and configuring the tiled cache +** - Using the tiled barrier for relaxing ordering to the tiles generated by the binner (as opposed to a full fragment barrier) +** - Custom composition step reading the output of previous rendering passes as textures +*/ + +// Sample Framework headers +#include "SampleFramework/CApplication.h" +#include "SampleFramework/CMemPool.h" +#include "SampleFramework/CShader.h" +#include "SampleFramework/CCmdMemRing.h" +#include "SampleFramework/CDescriptorSet.h" +#include "SampleFramework/FileLoader.h" + +// C++ standard library headers +#include +#include + +// GLM headers +#define GLM_FORCE_DEFAULT_ALIGNED_GENTYPES // Enforces GLSL std140/std430 alignment rules for glm types +#define GLM_FORCE_INTRINSICS // Enables usage of SIMD CPU instructions (requiring the above as well) +#include +#include +#include +#include + +namespace +{ + struct Vertex + { + float position[3]; + float normal[3]; + }; + + constexpr std::array VertexAttribState = + { + DkVtxAttribState{ 0, 0, offsetof(Vertex, position), DkVtxAttribSize_3x32, DkVtxAttribType_Float, 0 }, + DkVtxAttribState{ 0, 0, offsetof(Vertex, normal), DkVtxAttribSize_3x32, DkVtxAttribType_Float, 0 }, + }; + + constexpr std::array VertexBufferState = + { + DkVtxBufferState{ sizeof(Vertex), 0 }, + }; + + struct Transformation + { + glm::mat4 mdlvMtx; + glm::mat4 projMtx; + }; + + struct Lighting + { + glm::vec4 lightPos; // if w=0 this is lightDir + glm::vec3 ambient; + glm::vec3 diffuse; + glm::vec4 specular; // w is shininess + }; + + inline float fractf(float x) + { + return x - floorf(x); + } +} + +class CExample08 final : public CApplication +{ + static constexpr unsigned NumFramebuffers = 2; + static constexpr unsigned StaticCmdSize = 0x10000; + static constexpr unsigned DynamicCmdSize = 0x10000; + static constexpr unsigned MaxImages = 3; + static constexpr unsigned MaxSamplers = 1; + + dk::UniqueDevice device; + dk::UniqueQueue queue; + + std::optional pool_images; + std::optional pool_code; + std::optional pool_data; + + dk::UniqueCmdBuf cmdbuf; + dk::UniqueCmdBuf dyncmd; + CCmdMemRing dynmem; + + CDescriptorSet imageDescriptorSet; + CDescriptorSet samplerDescriptorSet; + + CShader vertexShader; + CShader fragmentShader; + + CShader compositionVertexShader; + CShader compositionFragmentShader; + + Transformation transformState; + CMemPool::Handle transformUniformBuffer; + + Lighting lightingState; + CMemPool::Handle lightingUniformBuffer; + + CMemPool::Handle vertexBuffer; + CMemPool::Handle indexBuffer; + + uint32_t framebufferWidth; + uint32_t framebufferHeight; + + CMemPool::Handle albedoBuffer_mem; + CMemPool::Handle normalBuffer_mem; + CMemPool::Handle viewDirBuffer_mem; + CMemPool::Handle depthBuffer_mem; + CMemPool::Handle framebuffers_mem[NumFramebuffers]; + + dk::Image albedoBuffer; + dk::Image normalBuffer; + dk::Image viewDirBuffer; + dk::Image depthBuffer; + dk::Image framebuffers[NumFramebuffers]; + DkCmdList framebuffer_cmdlists[NumFramebuffers]; + dk::UniqueSwapchain swapchain; + + DkCmdList render_cmdlist, composition_cmdlist; + +public: + CExample08() + { + // Create the deko3d device + device = dk::DeviceMaker{}.create(); + + // Create the main queue + queue = dk::QueueMaker{device}.setFlags(DkQueueFlags_Graphics).create(); + + // Create the memory pools + pool_images.emplace(device, DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image, 64*1024*1024); + pool_code.emplace(device, DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached | DkMemBlockFlags_Code, 1*1024*1024); + pool_data.emplace(device, DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached, 1*1024*1024); + + // Create the static command buffer and feed it freshly allocated memory + cmdbuf = dk::CmdBufMaker{device}.create(); + CMemPool::Handle cmdmem = pool_data->allocate(StaticCmdSize); + cmdbuf.addMemory(cmdmem.getMemBlock(), cmdmem.getOffset(), cmdmem.getSize()); + + // Create the dynamic command buffer and allocate memory for it + dyncmd = dk::CmdBufMaker{device}.create(); + dynmem.allocate(*pool_data, DynamicCmdSize); + + // Create the image and sampler descriptor sets + imageDescriptorSet.allocate(*pool_data); + samplerDescriptorSet.allocate(*pool_data); + + // Load the shaders + vertexShader.load(*pool_code, "romfs:/shaders/transform_normal_vsh.dksh"); + fragmentShader.load(*pool_code, "romfs:/shaders/basic_deferred_fsh.dksh"); + compositionVertexShader.load(*pool_code, "romfs:/shaders/composition_vsh.dksh"); + compositionFragmentShader.load(*pool_code, "romfs:/shaders/composition_fsh.dksh"); + + // Create the transformation uniform buffer + transformUniformBuffer = pool_data->allocate(sizeof(transformState), DK_UNIFORM_BUF_ALIGNMENT); + + // Create the lighting uniform buffer + lightingUniformBuffer = pool_data->allocate(sizeof(lightingState), DK_UNIFORM_BUF_ALIGNMENT); + + // Initialize the lighting state + lightingState.lightPos = glm::vec4{0.0f, 4.0f, 1.0f, 1.0f}; + lightingState.ambient = glm::vec3{0.046227f,0.028832f,0.003302f}; + lightingState.diffuse = glm::vec3{0.564963f,0.367818f,0.051293f}; + lightingState.specular = glm::vec4{24.0f*glm::vec3{0.394737f,0.308916f,0.134004f}, 64.0f}; + + // Load the teapot mesh + vertexBuffer = LoadFile(*pool_data, "romfs:/teapot-vtx.bin", alignof(Vertex)); + indexBuffer = LoadFile(*pool_data, "romfs:/teapot-idx.bin", alignof(u16)); + + // Configure persistent state in the queue + { + // Bind the image and sampler descriptor sets + imageDescriptorSet.bindForImages(cmdbuf); + samplerDescriptorSet.bindForSamplers(cmdbuf); + + // Enable the tiled cache + cmdbuf.setTileSize(64, 64); // example size, please experiment with this + cmdbuf.tiledCacheOp(DkTiledCacheOp_Enable); + + // Submit the configuration commands to the queue + queue.submitCommands(cmdbuf.finishList()); + queue.waitIdle(); + cmdbuf.clear(); + } + } + + void createFramebufferResources() + { + // Calculate layout for the different buffers part of the g-buffer + dk::ImageLayout layout_gbuffer; + dk::ImageLayoutMaker{device} + .setFlags(DkImageFlags_UsageRender | DkImageFlags_HwCompression) + .setFormat(DkImageFormat_RGBA16_Float) + .setDimensions(framebufferWidth, framebufferHeight) + .initialize(layout_gbuffer); + + // Calculate layout for the depth buffer + dk::ImageLayout layout_depthbuffer; + dk::ImageLayoutMaker{device} + .setFlags(DkImageFlags_UsageRender | DkImageFlags_HwCompression) + .setFormat(DkImageFormat_Z24S8) + .setDimensions(framebufferWidth, framebufferHeight) + .initialize(layout_depthbuffer); + + // Calculate layout for the framebuffers + dk::ImageLayout layout_framebuffer; + dk::ImageLayoutMaker{device} + .setFlags(DkImageFlags_UsageRender | DkImageFlags_UsagePresent) + .setFormat(DkImageFormat_RGBA8_Unorm_sRGB) + .setDimensions(framebufferWidth, framebufferHeight) + .initialize(layout_framebuffer); + + // Create the albedo buffer + albedoBuffer_mem = pool_images->allocate(layout_gbuffer.getSize(), layout_gbuffer.getAlignment()); + albedoBuffer.initialize(layout_gbuffer, albedoBuffer_mem.getMemBlock(), albedoBuffer_mem.getOffset()); + + // Create the normal buffer + normalBuffer_mem = pool_images->allocate(layout_gbuffer.getSize(), layout_gbuffer.getAlignment()); + normalBuffer.initialize(layout_gbuffer, normalBuffer_mem.getMemBlock(), normalBuffer_mem.getOffset()); + + // Create the view direction buffer + viewDirBuffer_mem = pool_images->allocate(layout_gbuffer.getSize(), layout_gbuffer.getAlignment()); + viewDirBuffer.initialize(layout_gbuffer, viewDirBuffer_mem.getMemBlock(), viewDirBuffer_mem.getOffset()); + + // Create the depth buffer + depthBuffer_mem = pool_images->allocate(layout_depthbuffer.getSize(), layout_depthbuffer.getAlignment()); + depthBuffer.initialize(layout_depthbuffer, depthBuffer_mem.getMemBlock(), depthBuffer_mem.getOffset()); + + // Create the framebuffers + uint64_t fb_size = layout_framebuffer.getSize(); + uint32_t fb_align = layout_framebuffer.getAlignment(); + DkImage const* fb_array[NumFramebuffers]; + for (unsigned i = 0; i < NumFramebuffers; i ++) + { + // Allocate a framebuffer + framebuffers_mem[i] = pool_images->allocate(fb_size, fb_align); + framebuffers[i].initialize(layout_framebuffer, framebuffers_mem[i].getMemBlock(), framebuffers_mem[i].getOffset()); + + // Generate a command list that binds the framebuffer + dk::ImageView framebufferView { framebuffers[i] }; + cmdbuf.bindRenderTargets(&framebufferView); + framebuffer_cmdlists[i] = cmdbuf.finishList(); + + // Fill in the array for use later by the swapchain creation code + fb_array[i] = &framebuffers[i]; + } + + // Create the swapchain using the framebuffers + swapchain = dk::SwapchainMaker{device, nwindowGetDefault(), fb_array, NumFramebuffers}.create(); + + // Generate the static command lists + recordStaticCommands(); + + // Initialize the projection matrix + transformState.projMtx = glm::perspectiveRH_ZO( + glm::radians(40.0f), + float(framebufferWidth)/float(framebufferHeight), + 0.01f, 1000.0f); + } + + void destroyFramebufferResources() + { + // Return early if we have nothing to destroy + if (!swapchain) return; + + // Make sure the queue is idle before destroying anything + queue.waitIdle(); + + // Clear the static cmdbuf, destroying the static cmdlists in the process + cmdbuf.clear(); + + // Destroy the swapchain + swapchain.destroy(); + + // Destroy the framebuffers + for (unsigned i = 0; i < NumFramebuffers; i ++) + framebuffers_mem[i].destroy(); + + // Destroy the rendertargets + depthBuffer_mem.destroy(); + viewDirBuffer_mem.destroy(); + normalBuffer_mem.destroy(); + albedoBuffer_mem.destroy(); + } + + ~CExample08() + { + // Destory the framebuffer resources + destroyFramebufferResources(); + + // Destroy the index buffer (not strictly needed in this case) + indexBuffer.destroy(); + + // Destroy the vertex buffer (not strictly needed in this case) + vertexBuffer.destroy(); + + // Destroy the uniform buffers (not strictly needed in this case) + lightingUniformBuffer.destroy(); + transformUniformBuffer.destroy(); + } + + void recordStaticCommands() + { + // Initialize state structs with deko3d defaults + dk::RasterizerState rasterizerState; + dk::ColorState colorState; + dk::ColorWriteState colorWriteState; + dk::DepthStencilState depthStencilState; + + // Bind g-buffer and depth buffer + dk::ImageView albedoTarget { albedoBuffer }, normalTarget { normalBuffer }, viewDirTarget { viewDirBuffer }, depthTarget { depthBuffer }; + cmdbuf.bindRenderTargets({ &albedoTarget, &normalTarget, &viewDirTarget }, &depthTarget); + + // Configure viewport and scissor + const DkViewport viewport = { 0.0f, 0.0f, float(framebufferWidth), float(framebufferHeight), 0.0f, 1.0f }; + const DkScissor scissor = { 0, 0, framebufferWidth, framebufferHeight }; + cmdbuf.setViewports(0, { viewport, viewport, viewport }); + cmdbuf.setScissors(0, { scissor, scissor, scissor }); + + // Clear the g-buffer and the depth buffer + cmdbuf.clearColor(0, DkColorMask_RGBA, 0.0f, 0.0f, 0.0f, 0.0f); + cmdbuf.clearColor(1, DkColorMask_RGBA, 0.0f, 0.0f, 0.0f, 0.0f); + cmdbuf.clearColor(2, DkColorMask_RGBA, 0.0f, 0.0f, 0.0f, 0.0f); + cmdbuf.clearDepthStencil(true, 1.0f, 0xFF, 0); + + // Bind state required for drawing the mesh + cmdbuf.bindShaders(DkStageFlag_GraphicsMask, { vertexShader, fragmentShader }); + cmdbuf.bindUniformBuffer(DkStage_Vertex, 0, transformUniformBuffer.getGpuAddr(), transformUniformBuffer.getSize()); + cmdbuf.bindRasterizerState(rasterizerState); + cmdbuf.bindColorState(colorState); + cmdbuf.bindColorWriteState(colorWriteState); + cmdbuf.bindDepthStencilState(depthStencilState); + cmdbuf.bindVtxBuffer(0, vertexBuffer.getGpuAddr(), vertexBuffer.getSize()); + cmdbuf.bindVtxAttribState(VertexAttribState); + cmdbuf.bindVtxBufferState(VertexBufferState); + cmdbuf.bindIdxBuffer(DkIdxFormat_Uint16, indexBuffer.getGpuAddr()); + + // Draw the mesh + cmdbuf.drawIndexed(DkPrimitive_Triangles, indexBuffer.getSize() / sizeof(u16), 1, 0, 0, 0); + + // Tiled barrier (similar to using Vulkan's vkCmdNextSubpass) + image cache + // flush so that the next rendering step can access the output from this step + cmdbuf.barrier(DkBarrier_Tiles, DkInvalidateFlags_Image); + + // Discard the depth buffer since we don't need it anymore + cmdbuf.discardDepthStencil(); + + // End of the main rendering command list + render_cmdlist = cmdbuf.finishList(); + + // Upload image descriptors + std::array descriptors; + descriptors[0].initialize(albedoTarget); + descriptors[1].initialize(normalTarget); + descriptors[2].initialize(viewDirTarget); + imageDescriptorSet.update(cmdbuf, 0, descriptors); + + // Upload sampler descriptor + dk::Sampler sampler; + dk::SamplerDescriptor samplerDescriptor; + samplerDescriptor.initialize(sampler); + samplerDescriptorSet.update(cmdbuf, 0, samplerDescriptor); + + // Flush the descriptor cache + cmdbuf.barrier(DkBarrier_None, DkInvalidateFlags_Descriptors); + + // Bind state required for doing the composition + cmdbuf.setViewports(0, viewport); + cmdbuf.setScissors(0, scissor); + cmdbuf.bindShaders(DkStageFlag_GraphicsMask, { compositionVertexShader, compositionFragmentShader }); + cmdbuf.bindUniformBuffer(DkStage_Fragment, 0, lightingUniformBuffer.getGpuAddr(), lightingUniformBuffer.getSize()); + cmdbuf.bindTextures(DkStage_Fragment, 0, { + dkMakeTextureHandle(0, 0), + dkMakeTextureHandle(1, 0), + dkMakeTextureHandle(2, 0), + }); + cmdbuf.bindRasterizerState(dk::RasterizerState{}); + cmdbuf.bindColorState(dk::ColorState{}); + cmdbuf.bindColorWriteState(dk::ColorWriteState{}); + cmdbuf.bindVtxAttribState({}); + + // Draw the full screen quad + cmdbuf.draw(DkPrimitive_Quads, 4, 1, 0, 0); + + // Tiled barrier + cmdbuf.barrier(DkBarrier_Tiles, 0); + + // Discard the g-buffer since we don't need it anymore + cmdbuf.bindRenderTargets({ &albedoTarget, &normalTarget, &viewDirTarget }); + cmdbuf.discardColor(0); + cmdbuf.discardColor(1); + cmdbuf.discardColor(2); + + // End of the composition cmdlist + composition_cmdlist = cmdbuf.finishList(); + } + + void render() + { + // Begin generating the dynamic command list, for commands that need to be sent only this frame specifically + dynmem.begin(dyncmd); + + // Update the transformation uniform buffer with the new state (this data gets inlined in the command list) + dyncmd.pushConstants( + transformUniformBuffer.getGpuAddr(), transformUniformBuffer.getSize(), + 0, sizeof(transformState), &transformState); + + // Update the lighting uniform buffer with the new state + dyncmd.pushConstants( + lightingUniformBuffer.getGpuAddr(), lightingUniformBuffer.getSize(), + 0, sizeof(lightingState), &lightingState); + + // Finish off the dynamic command list (which also submits it to the queue) + queue.submitCommands(dynmem.end(dyncmd)); + + // Run the main rendering command list + queue.submitCommands(render_cmdlist); + + // Acquire a framebuffer from the swapchain + int slot = queue.acquireImage(swapchain); + + // Submit the command list that binds the correct framebuffer + queue.submitCommands(framebuffer_cmdlists[slot]); + + // Submit the command list used for performing the composition + queue.submitCommands(composition_cmdlist); + + // Now that we are done rendering, present it to the screen (this also flushes the queue) + queue.presentImage(swapchain, slot); + } + + void onOperationMode(AppletOperationMode mode) override + { + // Destroy the framebuffer resources + destroyFramebufferResources(); + + // Choose framebuffer size + chooseFramebufferSize(framebufferWidth, framebufferHeight, mode); + + // Recreate the framebuffers and its associated resources + createFramebufferResources(); + } + + bool onFrame(u64 ns) override + { + hidScanInput(); + u64 kDown = hidKeysDown(CONTROLLER_P1_AUTO); + if (kDown & KEY_PLUS) + return false; + + float time = ns / 1000000000.0; // double precision division; followed by implicit cast to single precision + float tau = glm::two_pi(); + + float period1 = fractf(time/8.0f); + float period2 = fractf(time/4.0f); + + // Generate the model-view matrix for this frame + // Keep in mind that GLM transformation functions multiply to the right, so essentially we have: + // mdlvMtx = Translate * RotateX * RotateY * Translate + // This means that the Scale operation is applied first, then RotateY, and so on. + transformState.mdlvMtx = glm::mat4{1.0f}; + transformState.mdlvMtx = glm::translate(transformState.mdlvMtx, glm::vec3{sinf(period1*tau), 0.0f, -3.0f}); + transformState.mdlvMtx = glm::rotate(transformState.mdlvMtx, sinf(period2 * tau) * tau / 8.0f, glm::vec3{1.0f, 0.0f, 0.0f}); + transformState.mdlvMtx = glm::rotate(transformState.mdlvMtx, -period1 * tau, glm::vec3{0.0f, 1.0f, 0.0f}); + transformState.mdlvMtx = glm::translate(transformState.mdlvMtx, glm::vec3{0.0f, -0.5f, 0.0f}); + + render(); + return true; + } +}; + +void Example08(void) +{ + CExample08 app; + app.run(); +} diff --git a/graphics/deko3d/deko_examples/source/Example09_SimpleCompute.cpp b/graphics/deko3d/deko_examples/source/Example09_SimpleCompute.cpp new file mode 100644 index 0000000..324ca8a --- /dev/null +++ b/graphics/deko3d/deko_examples/source/Example09_SimpleCompute.cpp @@ -0,0 +1,318 @@ +/* +** deko3d Example 09: Simple Compute Shader (Geometry Generation) +** This example shows how to use a compute shader to dynamically generate geometry. +** New concepts in this example: +** - Enabling compute support in a queue +** - Configuring and using compute shaders +** - Setting up shader storage buffers (SSBOs) +** - Dispatching compute jobs +** - Using a primitive barrier to ensure ordering of items +** - Drawing geometry generated dynamically by the GPU itself +*/ + +// Sample Framework headers +#include "SampleFramework/CApplication.h" +#include "SampleFramework/CMemPool.h" +#include "SampleFramework/CShader.h" +#include "SampleFramework/CCmdMemRing.h" + +// C++ standard library headers +#include +#include + +// GLM headers +#define GLM_FORCE_DEFAULT_ALIGNED_GENTYPES // Enforces GLSL std140/std430 alignment rules for glm types +#define GLM_FORCE_INTRINSICS // Enables usage of SIMD CPU instructions (requiring the above as well) +#include +#include + +namespace +{ + struct Vertex + { + float position[4]; + float color[4]; + }; + + constexpr std::array VertexAttribState = + { + DkVtxAttribState{ 0, 0, offsetof(Vertex, position), DkVtxAttribSize_4x32, DkVtxAttribType_Float, 0 }, + DkVtxAttribState{ 0, 0, offsetof(Vertex, color), DkVtxAttribSize_4x32, DkVtxAttribType_Float, 0 }, + }; + + constexpr std::array VertexBufferState = + { + DkVtxBufferState{ sizeof(Vertex), 0 }, + }; + + struct GeneratorParams + { + glm::vec4 colorA; + glm::vec4 colorB; + float offset; + float scale; + float padding[2]; + }; + + inline float fractf(float x) + { + return x - floorf(x); + } +} + +class CExample09 final : public CApplication +{ + static constexpr unsigned NumFramebuffers = 2; + static constexpr uint32_t FramebufferWidth = 1280; + static constexpr uint32_t FramebufferHeight = 720; + static constexpr unsigned StaticCmdSize = 0x10000; + static constexpr unsigned DynamicCmdSize = 0x10000; + static constexpr unsigned NumVertices = 256; + + dk::UniqueDevice device; + dk::UniqueQueue queue; + + std::optional pool_images; + std::optional pool_code; + std::optional pool_data; + + dk::UniqueCmdBuf cmdbuf; + dk::UniqueCmdBuf dyncmd; + CCmdMemRing dynmem; + + GeneratorParams params; + CMemPool::Handle paramsUniformBuffer; + + CShader computeShader; + CShader vertexShader; + CShader fragmentShader; + + CMemPool::Handle vertexBuffer; + + CMemPool::Handle framebuffers_mem[NumFramebuffers]; + dk::Image framebuffers[NumFramebuffers]; + DkCmdList framebuffer_cmdlists[NumFramebuffers]; + dk::UniqueSwapchain swapchain; + + DkCmdList compute_cmdlist, render_cmdlist; + +public: + CExample09() + { + // Create the deko3d device + device = dk::DeviceMaker{}.create(); + + // Create the main queue + queue = dk::QueueMaker{device}.setFlags(DkQueueFlags_Graphics | DkQueueFlags_Compute).create(); + + // Create the memory pools + pool_images.emplace(device, DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image, 16*1024*1024); + pool_code.emplace(device, DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached | DkMemBlockFlags_Code, 128*1024); + pool_data.emplace(device, DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached, 1*1024*1024); + + // Create the static command buffer and feed it freshly allocated memory + cmdbuf = dk::CmdBufMaker{device}.create(); + CMemPool::Handle cmdmem = pool_data->allocate(StaticCmdSize); + cmdbuf.addMemory(cmdmem.getMemBlock(), cmdmem.getOffset(), cmdmem.getSize()); + + // Create the dynamic command buffer and allocate memory for it + dyncmd = dk::CmdBufMaker{device}.create(); + dynmem.allocate(*pool_data, DynamicCmdSize); + + // Load the shaders + computeShader.load(*pool_code, "romfs:/shaders/sinewave.dksh"); + vertexShader.load(*pool_code, "romfs:/shaders/basic_vsh.dksh"); + fragmentShader.load(*pool_code, "romfs:/shaders/color_fsh.dksh"); + + // Create the uniform buffer + paramsUniformBuffer = pool_data->allocate(sizeof(params), DK_UNIFORM_BUF_ALIGNMENT); + + // Initialize the params + params.colorA = glm::vec4 { 1.0f, 0.0f, 1.0f, 1.0f }; + params.colorB = glm::vec4 { 0.0f, 1.0f, 0.0f, 1.0f }; + params.offset = 0.0f; + params.scale = 1.0f; + + // Allocate memory for the vertex buffer + vertexBuffer = pool_data->allocate(sizeof(Vertex)*NumVertices, alignof(Vertex)); + + // Create the framebuffer resources + createFramebufferResources(); + } + + ~CExample09() + { + // Destroy the framebuffer resources + destroyFramebufferResources(); + + // Destroy the vertex buffer (not strictly needed in this case) + vertexBuffer.destroy(); + + // Destroy the uniform buffer (not strictly needed in this case) + paramsUniformBuffer.destroy(); + } + + void createFramebufferResources() + { + // Create layout for the framebuffers + dk::ImageLayout layout_framebuffer; + dk::ImageLayoutMaker{device} + .setFlags(DkImageFlags_UsageRender | DkImageFlags_UsagePresent | DkImageFlags_HwCompression) + .setFormat(DkImageFormat_RGBA8_Unorm) + .setDimensions(FramebufferWidth, FramebufferHeight) + .initialize(layout_framebuffer); + + // Create the framebuffers + std::array fb_array; + uint64_t fb_size = layout_framebuffer.getSize(); + uint32_t fb_align = layout_framebuffer.getAlignment(); + for (unsigned i = 0; i < NumFramebuffers; i ++) + { + // Allocate a framebuffer + framebuffers_mem[i] = pool_images->allocate(fb_size, fb_align); + framebuffers[i].initialize(layout_framebuffer, framebuffers_mem[i].getMemBlock(), framebuffers_mem[i].getOffset()); + + // Generate a command list that binds it + dk::ImageView colorTarget{ framebuffers[i] }; + cmdbuf.bindRenderTargets(&colorTarget); + framebuffer_cmdlists[i] = cmdbuf.finishList(); + + // Fill in the array for use later by the swapchain creation code + fb_array[i] = &framebuffers[i]; + } + + // Create the swapchain using the framebuffers + swapchain = dk::SwapchainMaker{device, nwindowGetDefault(), fb_array}.create(); + + // Generate the main rendering cmdlist + recordStaticCommands(); + } + + void destroyFramebufferResources() + { + // Return early if we have nothing to destroy + if (!swapchain) return; + + // Make sure the queue is idle before destroying anything + queue.waitIdle(); + + // Clear the static cmdbuf, destroying the static cmdlists in the process + cmdbuf.clear(); + + // Destroy the swapchain + swapchain.destroy(); + + // Destroy the framebuffers + for (unsigned i = 0; i < NumFramebuffers; i ++) + framebuffers_mem[i].destroy(); + } + + void recordStaticCommands() + { + // Bind state required for running the compute job + cmdbuf.bindShaders(DkStageFlag_Compute, { computeShader }); + cmdbuf.bindUniformBuffer(DkStage_Compute, 0, paramsUniformBuffer.getGpuAddr(), paramsUniformBuffer.getSize()); + cmdbuf.bindStorageBuffer(DkStage_Compute, 0, vertexBuffer.getGpuAddr(), vertexBuffer.getSize()); + + // Run the compute job + cmdbuf.dispatchCompute(NumVertices/32, 1, 1); + + // Place a barrier + cmdbuf.barrier(DkBarrier_Primitives, 0); + + // Finish off this command list + compute_cmdlist = cmdbuf.finishList(); + + // Initialize state structs with deko3d defaults + dk::RasterizerState rasterizerState; + dk::ColorState colorState; + dk::ColorWriteState colorWriteState; + dk::BlendState blendState; + + // Configure rasterizer state: enable polygon smoothing + rasterizerState.setPolygonSmoothEnable(true); + + // Configure color state: enable blending (needed for polygon smoothing since it generates alpha values) + colorState.setBlendEnable(0, true); + + // Configure viewport and scissor + cmdbuf.setViewports(0, { { 0.0f, 0.0f, FramebufferWidth, FramebufferHeight, 0.0f, 1.0f } }); + cmdbuf.setScissors(0, { { 0, 0, FramebufferWidth, FramebufferHeight } }); + + // Clear the color buffer + cmdbuf.clearColor(0, DkColorMask_RGBA, 0.0f, 0.0f, 0.0f, 0.0f); + + // Bind state required for drawing the triangle + cmdbuf.bindShaders(DkStageFlag_GraphicsMask, { vertexShader, fragmentShader }); + cmdbuf.bindRasterizerState(rasterizerState); + cmdbuf.bindColorState(colorState); + cmdbuf.bindColorWriteState(colorWriteState); + cmdbuf.bindBlendStates(0, blendState); + cmdbuf.bindVtxBuffer(0, vertexBuffer.getGpuAddr(), vertexBuffer.getSize()); + cmdbuf.bindVtxAttribState(VertexAttribState); + cmdbuf.bindVtxBufferState(VertexBufferState); + cmdbuf.setLineWidth(16.0f); + + // Draw the line + cmdbuf.draw(DkPrimitive_LineStrip, NumVertices, 1, 0, 0); + + // Finish off this command list + render_cmdlist = cmdbuf.finishList(); + } + + void render() + { + // Begin generating the dynamic command list, for commands that need to be sent only this frame specifically + dynmem.begin(dyncmd); + + // Update the uniform buffer with the new state (this data gets inlined in the command list) + dyncmd.pushConstants( + paramsUniformBuffer.getGpuAddr(), paramsUniformBuffer.getSize(), + 0, sizeof(params), ¶ms); + + // Finish off the dynamic command list (which also submits it to the queue) + queue.submitCommands(dynmem.end(dyncmd)); + + // Run the compute command list + queue.submitCommands(compute_cmdlist); + + // Acquire a framebuffer from the swapchain (and wait for it to be available) + int slot = queue.acquireImage(swapchain); + + // Run the command list that attaches said framebuffer to the queue + queue.submitCommands(framebuffer_cmdlists[slot]); + + // Run the main rendering command list + queue.submitCommands(render_cmdlist); + + // Now that we are done rendering, present it to the screen + queue.presentImage(swapchain, slot); + } + + bool onFrame(u64 ns) override + { + hidScanInput(); + u64 kDown = hidKeysDown(CONTROLLER_P1_AUTO); + if (kDown & KEY_PLUS) + return false; + + float time = ns / 1000000000.0; // double precision division; followed by implicit cast to single precision + float tau = glm::two_pi(); + + params.offset = fractf(time/4.0f); + + float xx = fractf(time * 135.0f / 60.0f / 2.0f); + params.scale = cosf(xx*tau); + params.colorA.g = powf(fabsf(params.scale), 4.0f); + params.colorB.g = 1.0f - params.colorA.g; + + render(); + return true; + } +}; + +void Example09(void) +{ + CExample09 app; + app.run(); +} diff --git a/graphics/deko3d/deko_examples/source/SampleFramework/CApplication.cpp b/graphics/deko3d/deko_examples/source/SampleFramework/CApplication.cpp new file mode 100644 index 0000000..34786a3 --- /dev/null +++ b/graphics/deko3d/deko_examples/source/SampleFramework/CApplication.cpp @@ -0,0 +1,69 @@ +/* +** Sample Framework for deko3d Applications +** CApplication.cpp: Wrapper class containing common application boilerplate +*/ +#include "CApplication.h" + +CApplication::CApplication() +{ + appletLockExit(); + appletSetFocusHandlingMode(AppletFocusHandlingMode_NoSuspend); +} + +CApplication::~CApplication() +{ + appletSetFocusHandlingMode(AppletFocusHandlingMode_SuspendHomeSleep); + appletUnlockExit(); +} + +void CApplication::run() +{ + u64 tick_ref = armGetSystemTick(); + u64 tick_saved = tick_ref; + bool focused = appletGetFocusState() == AppletFocusState_Focused; + + onOperationMode(appletGetOperationMode()); + + for (;;) + { + u32 msg = 0; + Result rc = appletGetMessage(&msg); + if (R_SUCCEEDED(rc)) + { + bool should_close = !appletProcessMessage(msg); + if (should_close) + return; + + switch (msg) + { + case AppletMessage_FocusStateChanged: + { + bool old_focused = focused; + AppletFocusState state = appletGetFocusState(); + focused = state == AppletFocusState_Focused; + + onFocusState(state); + if (focused == old_focused) + break; + if (focused) + { + appletSetFocusHandlingMode(AppletFocusHandlingMode_NoSuspend); + tick_ref += armGetSystemTick() - tick_saved; + } + else + { + tick_saved = armGetSystemTick(); + appletSetFocusHandlingMode(AppletFocusHandlingMode_SuspendHomeSleepNotify); + } + break; + } + case AppletMessage_OperationModeChanged: + onOperationMode(appletGetOperationMode()); + break; + } + } + + if (focused && !onFrame(armTicksToNs(armGetSystemTick() - tick_ref))) + break; + } +} diff --git a/graphics/deko3d/deko_examples/source/SampleFramework/CApplication.h b/graphics/deko3d/deko_examples/source/SampleFramework/CApplication.h new file mode 100644 index 0000000..2d80450 --- /dev/null +++ b/graphics/deko3d/deko_examples/source/SampleFramework/CApplication.h @@ -0,0 +1,38 @@ +/* +** Sample Framework for deko3d Applications +** CApplication.h: Wrapper class containing common application boilerplate +*/ +#pragma once +#include "common.h" + +class CApplication +{ +protected: + virtual void onFocusState(AppletFocusState) { } + virtual void onOperationMode(AppletOperationMode) { } + virtual bool onFrame(u64) { return true; } + +public: + CApplication(); + ~CApplication(); + + void run(); + + static constexpr void chooseFramebufferSize(uint32_t& width, uint32_t& height, AppletOperationMode mode); +}; + +constexpr void CApplication::chooseFramebufferSize(uint32_t& width, uint32_t& height, AppletOperationMode mode) +{ + switch (mode) + { + default: + case AppletOperationMode_Handheld: + width = 1280; + height = 720; + break; + case AppletOperationMode_Docked: + width = 1920; + height = 1080; + break; + } +} diff --git a/graphics/deko3d/deko_examples/source/SampleFramework/CCmdMemRing.h b/graphics/deko3d/deko_examples/source/SampleFramework/CCmdMemRing.h new file mode 100644 index 0000000..3a8e1a0 --- /dev/null +++ b/graphics/deko3d/deko_examples/source/SampleFramework/CCmdMemRing.h @@ -0,0 +1,57 @@ +/* +** Sample Framework for deko3d Applications +** CCmdMemRing.h: Memory provider class for dynamic command buffers +*/ +#pragma once +#include "common.h" +#include "CMemPool.h" + +template +class CCmdMemRing +{ + static_assert(NumSlices > 0, "Need a non-zero number of slices..."); + CMemPool::Handle m_mem; + unsigned m_curSlice; + dk::Fence m_fences[NumSlices]; +public: + CCmdMemRing() : m_mem{}, m_curSlice{}, m_fences{} { } + ~CCmdMemRing() + { + m_mem.destroy(); + } + + bool allocate(CMemPool& pool, uint32_t sliceSize) + { + sliceSize = (sliceSize + DK_CMDMEM_ALIGNMENT - 1) &~ (DK_CMDMEM_ALIGNMENT - 1); + m_mem = pool.allocate(NumSlices*sliceSize); + return m_mem; + } + + void begin(dk::CmdBuf cmdbuf) + { + // Clear/reset the command buffer, which also destroys all command list handles + // (but remember: it does *not* in fact destroy the command data) + cmdbuf.clear(); + + // Wait for the current slice of memory to be available, and feed it to the command buffer + uint32_t sliceSize = m_mem.getSize() / NumSlices; + m_fences[m_curSlice].wait(); + + // Feed the memory to the command buffer + cmdbuf.addMemory(m_mem.getMemBlock(), m_mem.getOffset() + m_curSlice * sliceSize, sliceSize); + } + + DkCmdList end(dk::CmdBuf cmdbuf) + { + // Signal the fence corresponding to the current slice; so that in the future when we want + // to use it again, we can wait for the completion of the commands we've just submitted + // (and as such we don't overwrite in-flight command data with new one) + cmdbuf.signalFence(m_fences[m_curSlice]); + + // Advance the current slice counter; wrapping around when we reach the end + m_curSlice = (m_curSlice + 1) % NumSlices; + + // Finish off the command list, returning it to the caller + return cmdbuf.finishList(); + } +}; diff --git a/graphics/deko3d/deko_examples/source/SampleFramework/CDescriptorSet.h b/graphics/deko3d/deko_examples/source/SampleFramework/CDescriptorSet.h new file mode 100644 index 0000000..a1c0ed9 --- /dev/null +++ b/graphics/deko3d/deko_examples/source/SampleFramework/CDescriptorSet.h @@ -0,0 +1,71 @@ +/* +** Sample Framework for deko3d Applications +** CDescriptorSet.h: Image/Sampler descriptor set class +*/ +#pragma once +#include "common.h" +#include "CMemPool.h" + +template +class CDescriptorSet +{ + static_assert(NumDescriptors > 0, "Need a non-zero number of descriptors..."); + static_assert(sizeof(DkImageDescriptor) == sizeof(DkSamplerDescriptor), "shouldn't happen"); + static_assert(DK_IMAGE_DESCRIPTOR_ALIGNMENT == DK_SAMPLER_DESCRIPTOR_ALIGNMENT, "shouldn't happen"); + static constexpr size_t DescriptorSize = sizeof(DkImageDescriptor); + static constexpr size_t DescriptorAlign = DK_IMAGE_DESCRIPTOR_ALIGNMENT; + + CMemPool::Handle m_mem; +public: + CDescriptorSet() : m_mem{} { } + ~CDescriptorSet() + { + m_mem.destroy(); + } + + bool allocate(CMemPool& pool) + { + m_mem = pool.allocate(NumDescriptors*DescriptorSize, DescriptorAlign); + return m_mem; + } + + void bindForImages(dk::CmdBuf cmdbuf) + { + cmdbuf.bindImageDescriptorSet(m_mem.getGpuAddr(), NumDescriptors); + } + + void bindForSamplers(dk::CmdBuf cmdbuf) + { + cmdbuf.bindSamplerDescriptorSet(m_mem.getGpuAddr(), NumDescriptors); + } + + template + void update(dk::CmdBuf cmdbuf, uint32_t id, T const& descriptor) + { + static_assert(sizeof(T) == DescriptorSize); + cmdbuf.pushData(m_mem.getGpuAddr() + id*DescriptorSize, &descriptor, DescriptorSize); + } + + template + void update(dk::CmdBuf cmdbuf, uint32_t id, std::array const& descriptors) + { + static_assert(sizeof(T) == DescriptorSize); + cmdbuf.pushData(m_mem.getGpuAddr() + id*DescriptorSize, descriptors.data(), descriptors.size()*DescriptorSize); + } + +#ifdef DK_HPP_SUPPORT_VECTOR + template > + void update(dk::CmdBuf cmdbuf, uint32_t id, std::vector const& descriptors) + { + static_assert(sizeof(T) == DescriptorSize); + cmdbuf.pushData(m_mem.getGpuAddr() + id*DescriptorSize, descriptors.data(), descriptors.size()*DescriptorSize); + } +#endif + + template + void update(dk::CmdBuf cmdbuf, uint32_t id, std::initializer_list const& descriptors) + { + static_assert(sizeof(T) == DescriptorSize); + cmdbuf.pushData(m_mem.getGpuAddr() + id*DescriptorSize, descriptors.data(), descriptors.size()*DescriptorSize); + } +}; diff --git a/graphics/deko3d/deko_examples/source/SampleFramework/CExternalImage.cpp b/graphics/deko3d/deko_examples/source/SampleFramework/CExternalImage.cpp new file mode 100644 index 0000000..37b6a26 --- /dev/null +++ b/graphics/deko3d/deko_examples/source/SampleFramework/CExternalImage.cpp @@ -0,0 +1,37 @@ +/* +** Sample Framework for deko3d Applications +** CExternalImage.cpp: Utility class for loading images from the filesystem +*/ +#include "CExternalImage.h" +#include "FileLoader.h" + +bool CExternalImage::load(CMemPool& imagePool, CMemPool& scratchPool, dk::Device device, dk::Queue transferQueue, const char* path, uint32_t width, uint32_t height, DkImageFormat format, uint32_t flags) +{ + CMemPool::Handle tempimgmem = LoadFile(scratchPool, path, DK_IMAGE_LINEAR_STRIDE_ALIGNMENT); + if (!tempimgmem) + return false; + + dk::UniqueCmdBuf tempcmdbuf = dk::CmdBufMaker{device}.create(); + CMemPool::Handle tempcmdmem = scratchPool.allocate(DK_MEMBLOCK_ALIGNMENT); + tempcmdbuf.addMemory(tempcmdmem.getMemBlock(), tempcmdmem.getOffset(), tempcmdmem.getSize()); + + dk::ImageLayout layout; + dk::ImageLayoutMaker{device} + .setFlags(flags) + .setFormat(format) + .setDimensions(width, height) + .initialize(layout); + + m_mem = imagePool.allocate(layout.getSize(), layout.getAlignment()); + m_image.initialize(layout, m_mem.getMemBlock(), m_mem.getOffset()); + m_descriptor.initialize(m_image); + + dk::ImageView imageView{m_image}; + tempcmdbuf.copyBufferToImage({ tempimgmem.getGpuAddr() }, imageView, { 0, 0, 0, width, height, 1 }); + transferQueue.submitCommands(tempcmdbuf.finishList()); + transferQueue.waitIdle(); + + tempcmdmem.destroy(); + tempimgmem.destroy(); + return true; +} diff --git a/graphics/deko3d/deko_examples/source/SampleFramework/CExternalImage.h b/graphics/deko3d/deko_examples/source/SampleFramework/CExternalImage.h new file mode 100644 index 0000000..230e2e9 --- /dev/null +++ b/graphics/deko3d/deko_examples/source/SampleFramework/CExternalImage.h @@ -0,0 +1,37 @@ +/* +** Sample Framework for deko3d Applications +** CExternalImage.h: Utility class for loading images from the filesystem +*/ +#pragma once +#include "common.h" +#include "CMemPool.h" + +class CExternalImage +{ + dk::Image m_image; + dk::ImageDescriptor m_descriptor; + CMemPool::Handle m_mem; +public: + CExternalImage() : m_image{}, m_descriptor{}, m_mem{} { } + ~CExternalImage() + { + m_mem.destroy(); + } + + constexpr operator bool() const + { + return m_mem; + } + + constexpr dk::Image& get() + { + return m_image; + } + + constexpr dk::ImageDescriptor const& getDescriptor() const + { + return m_descriptor; + } + + bool load(CMemPool& imagePool, CMemPool& scratchPool, dk::Device device, dk::Queue transferQueue, const char* path, uint32_t width, uint32_t height, DkImageFormat format, uint32_t flags = 0); +}; diff --git a/graphics/deko3d/deko_examples/source/SampleFramework/CIntrusiveList.h b/graphics/deko3d/deko_examples/source/SampleFramework/CIntrusiveList.h new file mode 100644 index 0000000..73eb5c8 --- /dev/null +++ b/graphics/deko3d/deko_examples/source/SampleFramework/CIntrusiveList.h @@ -0,0 +1,119 @@ +/* +** Sample Framework for deko3d Applications +** CIntrusiveList.h: Intrusive doubly-linked list helper class +*/ +#pragma once +#include "common.h" + +template +struct CIntrusiveListNode +{ + T *m_next, *m_prev; + + constexpr CIntrusiveListNode() : m_next{}, m_prev{} { } + constexpr operator bool() const { return m_next || m_prev; } +}; + +template T::* node_ptr> +class CIntrusiveList +{ + T *m_first, *m_last; + +public: + constexpr CIntrusiveList() : m_first{}, m_last{} { } + + constexpr T* first() const { return m_first; } + constexpr T* last() const { return m_last; } + constexpr bool empty() const { return !m_first; } + constexpr void clear() { m_first = m_last = nullptr; } + + constexpr bool isLinked(T* obj) const { return obj->*node_ptr || m_first == obj; } + constexpr T* prev(T* obj) const { return (obj->*node_ptr).m_prev; } + constexpr T* next(T* obj) const { return (obj->*node_ptr).m_next; } + + void add(T* obj) + { + return addBefore(nullptr, obj); + } + + void addBefore(T* pos, T* obj) + { + auto& node = obj->*node_ptr; + node.m_next = pos; + node.m_prev = pos ? (pos->*node_ptr).m_prev : m_last; + + if (pos) + (pos->*node_ptr).m_prev = obj; + else + m_last = obj; + + if (node.m_prev) + (node.m_prev->*node_ptr).m_next = obj; + else + m_first = obj; + } + + void addAfter(T* pos, T* obj) + { + auto& node = obj->*node_ptr; + node.m_next = pos ? (pos->*node_ptr).m_next : m_first; + node.m_prev = pos; + + if (pos) + (pos->*node_ptr).m_next = obj; + else + m_first = obj; + + if (node.m_next) + (node.m_next->*node_ptr).m_prev = obj; + else + m_last = obj; + } + + T* pop() + { + T* ret = m_first; + if (ret) + { + m_first = (ret->*node_ptr).m_next; + if (m_first) + (m_first->*node_ptr).m_prev = nullptr; + else + m_last = nullptr; + } + return ret; + } + + void remove(T* obj) + { + auto& node = obj->*node_ptr; + if (node.m_prev) + { + (node.m_prev->*node_ptr).m_next = node.m_next; + if (node.m_next) + (node.m_next->*node_ptr).m_prev = node.m_prev; + else + m_last = node.m_prev; + } else + { + m_first = node.m_next; + if (m_first) + (m_first->*node_ptr).m_prev = nullptr; + else + m_last = nullptr; + } + + node.m_next = node.m_prev = 0; + } + + template + void iterate(L lambda) const + { + T* next = nullptr; + for (T* cur = m_first; cur; cur = next) + { + next = (cur->*node_ptr).m_next; + lambda(cur); + } + } +}; diff --git a/graphics/deko3d/deko_examples/source/SampleFramework/CIntrusiveTree.cpp b/graphics/deko3d/deko_examples/source/SampleFramework/CIntrusiveTree.cpp new file mode 100644 index 0000000..9f21b63 --- /dev/null +++ b/graphics/deko3d/deko_examples/source/SampleFramework/CIntrusiveTree.cpp @@ -0,0 +1,214 @@ +/* +** Sample Framework for deko3d Applications +** CIntrusiveTree.cpp: Intrusive red-black tree helper class +*/ +#include "CIntrusiveTree.h" + +// This red-black tree implementation is mostly based on mtheall's work, +// which can be found here: +// https://github.com/smealum/ctrulib/tree/master/libctru/source/util/rbtree + +void CIntrusiveTreeBase::rotate(N* node, N::Leaf leaf) +{ + N *tmp = node->child(leaf); + N *parent = node->getParent(); + + node->child(leaf) = tmp->child(!leaf); + if (tmp->child(!leaf)) + tmp->child(!leaf)->setParent(node); + + tmp->child(!leaf) = node; + tmp->setParent(parent); + + if (parent) + { + if (node == parent->child(!leaf)) + parent->child(!leaf) = tmp; + else + parent->child(leaf) = tmp; + } + else + m_root = tmp; + + node->setParent(tmp); +} + +void CIntrusiveTreeBase::recolor(N* parent, N* node) +{ + N *sibling; + + while ((!node || node->isBlack()) && node != m_root) + { + N::Leaf leaf = node == parent->left() ? N::Right : N::Left; + sibling = parent->child(leaf); + + if (sibling->isRed()) + { + sibling->setBlack(); + parent->setRed(); + rotate(parent, leaf); + sibling = parent->child(leaf); + } + + N::Color clr[2]; + clr[N::Left] = sibling->left() ? sibling->left()->getColor() : N::Black; + clr[N::Right] = sibling->right() ? sibling->right()->getColor() : N::Black; + + if (clr[N::Left] == N::Black && clr[N::Right] == N::Black) + { + sibling->setRed(); + node = parent; + parent = node->getParent(); + } + else + { + if (clr[leaf] == N::Black) + { + sibling->child(!leaf)->setBlack(); + sibling->setRed(); + rotate(sibling, !leaf); + sibling = parent->child(leaf); + } + + sibling->setColor(parent->getColor()); + parent->setBlack(); + sibling->child(leaf)->setBlack(); + rotate(parent, leaf); + + node = m_root; + } + } + + if (node) + node->setBlack(); +} + +auto CIntrusiveTreeBase::walk(N* node, N::Leaf leaf) const -> N* +{ + if (node->child(leaf)) + { + node = node->child(leaf); + while (node->child(!leaf)) + node = node->child(!leaf); + } + else + { + N *parent = node->getParent(); + while (parent && node == parent->child(leaf)) + { + node = parent; + parent = node->getParent(); + } + node = parent; + } + + return node; +} + +void CIntrusiveTreeBase::insert(N* node, N* parent) +{ + node->left() = node->right() = nullptr; + node->setParent(parent); + node->setRed(); + + while ((parent = node->getParent()) && parent->isRed()) + { + N *grandparent = parent->getParent(); + N::Leaf leaf = parent == grandparent->left() ? N::Right : N::Left; + N *uncle = grandparent->child(leaf); + + if (uncle && uncle->isRed()) + { + uncle->setBlack(); + parent->setBlack(); + grandparent->setRed(); + + node = grandparent; + } + else + { + if (parent->child(leaf) == node) + { + rotate(parent, leaf); + + N* tmp = parent; + parent = node; + node = tmp; + } + + parent->setBlack(); + grandparent->setRed(); + rotate(grandparent, !leaf); + } + } + + m_root->setBlack(); +} + +void CIntrusiveTreeBase::remove(N* node) +{ + N::Color color; + N *child, *parent; + + if (node->left() && node->right()) + { + N *old = node; + + node = node->right(); + while (node->left()) + node = node->left(); + + parent = old->getParent(); + if (parent) + { + if (parent->left() == old) + parent->left() = node; + else + parent->right() = node; + } + else + m_root = node; + + child = node->right(); + parent = node->getParent(); + color = node->getColor(); + + if (parent == old) + parent = node; + else + { + if (child) + child->setParent(parent); + parent->left() = child; + + node->right() = old->right(); + old->right()->setParent(node); + } + + node->setParent(old->getParent()); + node->setColor(old->getColor()); + node->left() = old->left(); + old->left()->setParent(node); + } + else + { + child = node->left() ? node->right() : node->left(); + parent = node->getParent(); + color = node->getColor(); + + if (child) + child->setParent(parent); + if (parent) + { + if (parent->left() == node) + parent->left() = child; + else + parent->right() = child; + } + else + m_root = child; + } + + if (color == N::Black) + recolor(parent, child); +} diff --git a/graphics/deko3d/deko_examples/source/SampleFramework/CIntrusiveTree.h b/graphics/deko3d/deko_examples/source/SampleFramework/CIntrusiveTree.h new file mode 100644 index 0000000..9796ee6 --- /dev/null +++ b/graphics/deko3d/deko_examples/source/SampleFramework/CIntrusiveTree.h @@ -0,0 +1,250 @@ +/* +** Sample Framework for deko3d Applications +** CIntrusiveTree.h: Intrusive red-black tree helper class +*/ +#pragma once +#include "common.h" + +#include + +struct CIntrusiveTreeNode +{ + enum Color + { + Red, + Black, + }; + + enum Leaf + { + Left, + Right, + }; + +private: + uintptr_t m_parent_color; + CIntrusiveTreeNode* m_children[2]; + +public: + constexpr CIntrusiveTreeNode() : m_parent_color{}, m_children{} { } + + constexpr CIntrusiveTreeNode* getParent() const + { + return reinterpret_cast(m_parent_color &~ 1); + } + + void setParent(CIntrusiveTreeNode* parent) + { + m_parent_color = (m_parent_color & 1) | reinterpret_cast(parent); + } + + constexpr Color getColor() const + { + return static_cast(m_parent_color & 1); + } + + void setColor(Color color) + { + m_parent_color = (m_parent_color &~ 1) | static_cast(color); + } + + constexpr CIntrusiveTreeNode*& child(Leaf leaf) + { + return m_children[leaf]; + } + + constexpr CIntrusiveTreeNode* const& child(Leaf leaf) const + { + return m_children[leaf]; + } + + //-------------------------------------- + + constexpr bool isRed() const { return getColor() == Red; } + constexpr bool isBlack() const { return getColor() == Black; } + void setRed() { setColor(Red); } + void setBlack() { setColor(Black); } + + constexpr CIntrusiveTreeNode*& left() { return child(Left); } + constexpr CIntrusiveTreeNode*& right() { return child(Right); } + constexpr CIntrusiveTreeNode* const& left() const { return child(Left); } + constexpr CIntrusiveTreeNode* const& right() const { return child(Right); } +}; + +NX_CONSTEXPR CIntrusiveTreeNode::Leaf operator!(CIntrusiveTreeNode::Leaf val) noexcept +{ + return static_cast(!static_cast(val)); +} + +class CIntrusiveTreeBase +{ + using N = CIntrusiveTreeNode; + + void rotate(N* node, N::Leaf leaf); + void recolor(N* parent, N* node); +protected: + N* m_root; + + constexpr CIntrusiveTreeBase() : m_root{} { } + + N* walk(N* node, N::Leaf leaf) const; + void insert(N* node, N* parent); + void remove(N* node); + + N* minmax(N::Leaf leaf) const + { + N* p = m_root; + if (!p) + return nullptr; + while (p->child(leaf)) + p = p->child(leaf); + return p; + } + + template + N*& navigate(N*& node, N*& parent, N::Leaf leafOnEqual, H helm) const + { + node = nullptr; + parent = nullptr; + + N** point = const_cast(&m_root); + while (*point) + { + int direction = helm(*point); + parent = *point; + if (direction < 0) + point = &(*point)->left(); + else if (direction > 0) + point = &(*point)->right(); + else + { + node = *point; + point = &(*point)->child(leafOnEqual); + } + } + return *point; + } +}; + +template +constexpr ClassT* parent_obj(MemberT* member, MemberT ClassT::* ptr) +{ + union whatever + { + MemberT ClassT::* ptr; + intptr_t offset; + }; + // This is technically UB, but basically every compiler worth using admits it as an extension + return (ClassT*)((intptr_t)member - whatever{ptr}.offset); +} + +template < + typename T, + CIntrusiveTreeNode T::* node_ptr, + typename Comparator = std::less<> +> +class CIntrusiveTree final : protected CIntrusiveTreeBase +{ + using N = CIntrusiveTreeNode; + + static constexpr T* toType(N* m) + { + return m ? parent_obj(m, node_ptr) : nullptr; + } + + static constexpr N* toNode(T* m) + { + return m ? &(m->*node_ptr) : nullptr; + } + + template + static int compare(A const& a, B const& b) + { + Comparator comp; + if (comp(a, b)) + return -1; + if (comp(b, a)) + return 1; + return 0; + } + +public: + constexpr CIntrusiveTree() : CIntrusiveTreeBase{} { } + + T* first() const { return toType(minmax(N::Left)); } + T* last() const { return toType(minmax(N::Right)); } + bool empty() const { return m_root != nullptr; } + void clear() { m_root = nullptr; } + + T* prev(T* node) const { return toType(walk(toNode(node), N::Left)); } + T* next(T* node) const { return toType(walk(toNode(node), N::Right)); } + + enum SearchMode + { + Exact = 0, + LowerBound = 1, + UpperBound = 2, + }; + + template + T* search(SearchMode mode, Lambda lambda) const + { + N *node, *parent; + N*& point = navigate(node, parent, + mode != UpperBound ? N::Left : N::Right, + [&lambda](N* curnode) { return lambda(toType(curnode)); }); + + switch (mode) + { + default: + case Exact: + break; + case LowerBound: + if (!node && parent) + { + if (&parent->left() == &point) + node = parent; + else + node = walk(parent, N::Right); + } + break; + case UpperBound: + if (node) + node = walk(node, N::Right); + else if (parent) + { + if (&parent->right() == &point) + node = walk(parent, N::Right); + else + node = parent; + } + break; + } + return toType(node); + } + + template + T* find(K const& key, SearchMode mode = Exact) const + { + return search(mode, [&key](T* obj) { return compare(key, *obj); }); + } + + T* insert(T* obj, bool allow_dupes = false) + { + N *node, *parent; + N*& point = navigate(node, parent, N::Right, + [obj](N* curnode) { return compare(*obj, *toType(curnode)); }); + + if (node && !allow_dupes) + return toType(node); + + point = toNode(obj); + CIntrusiveTreeBase::insert(point, parent); + return obj; + } + + void remove(T* obj) + { + CIntrusiveTreeBase::remove(toNode(obj)); + } +}; diff --git a/graphics/deko3d/deko_examples/source/SampleFramework/CMemPool.cpp b/graphics/deko3d/deko_examples/source/SampleFramework/CMemPool.cpp new file mode 100644 index 0000000..fb3bd10 --- /dev/null +++ b/graphics/deko3d/deko_examples/source/SampleFramework/CMemPool.cpp @@ -0,0 +1,175 @@ +/* +** Sample Framework for deko3d Applications +** CMemPool.cpp: Pooled dynamic memory allocation manager class +*/ +#include "CMemPool.h" + +inline auto CMemPool::_newSlice() -> Slice* +{ + Slice* ret = m_sliceHeap.pop(); + if (!ret) ret = (Slice*)::malloc(sizeof(Slice)); + return ret; +} + +inline void CMemPool::_deleteSlice(Slice* s) +{ + if (!s) return; + m_sliceHeap.add(s); +} + +CMemPool::~CMemPool() +{ + m_memMap.iterate([](Slice* s) { ::free(s); }); + m_sliceHeap.iterate([](Slice* s) { ::free(s); }); + m_blocks.iterate([](Block* blk) { + blk->m_obj.destroy(); + ::free(blk); + }); +} + +auto CMemPool::allocate(uint32_t size, uint32_t alignment) -> Handle +{ + if (!size) return nullptr; + if (alignment & (alignment - 1)) return nullptr; + size = (size + alignment - 1) &~ (alignment - 1); +#ifdef DEBUG_CMEMPOOL + printf("Allocating size=%u alignment=0x%x\n", size, alignment); + { + Slice* temp = /*m_freeList*/m_memMap.first(); + while (temp) + { + printf("-- blk %p | 0x%08x-0x%08x | %s used\n", temp->m_block, temp->m_start, temp->m_end, temp->m_pool ? " " : "not"); + temp = /*m_freeList*/m_memMap.next(temp); + } + } +#endif + + uint32_t start_offset = 0; + uint32_t end_offset = 0; + Slice* slice = m_freeList.find(size, decltype(m_freeList)::LowerBound); + while (slice) + { +#ifdef DEBUG_CMEMPOOL + printf(" * Checking slice 0x%x - 0x%x\n", slice->m_start, slice->m_end); +#endif + start_offset = (slice->m_start + alignment - 1) &~ (alignment - 1); + end_offset = start_offset + size; + if (end_offset <= slice->m_end) + break; + slice = m_freeList.next(slice); + } + + if (!slice) + { + Block* blk = (Block*)::malloc(sizeof(Block)); + if (!blk) + return nullptr; + + uint32_t unusableSize = (m_flags & DkMemBlockFlags_Code) ? DK_SHADER_CODE_UNUSABLE_SIZE : 0; + uint32_t blkSize = m_blockSize - unusableSize; + blkSize = size > blkSize ? size : blkSize; + blkSize = (blkSize + unusableSize + DK_MEMBLOCK_ALIGNMENT - 1) &~ (DK_MEMBLOCK_ALIGNMENT - 1); +#ifdef DEBUG_CMEMPOOL + printf(" ! Allocating block of size 0x%x\n", blkSize); +#endif + blk->m_obj = dk::MemBlockMaker{m_dev, blkSize}.setFlags(m_flags).create(); + if (!blk->m_obj) + { + ::free(blk); + return nullptr; + } + + slice = _newSlice(); + if (!slice) + { + blk->m_obj.destroy(); + ::free(blk); + return nullptr; + } + + slice->m_pool = nullptr; + slice->m_block = blk; + slice->m_start = 0; + slice->m_end = blkSize - unusableSize; + m_memMap.add(slice); + + blk->m_cpuAddr = blk->m_obj.getCpuAddr(); + blk->m_gpuAddr = blk->m_obj.getGpuAddr(); + m_blocks.add(blk); + + start_offset = 0; + end_offset = size; + } + else + { +#ifdef DEBUG_CMEMPOOL + printf(" * found it\n"); +#endif + m_freeList.remove(slice); + } + + if (start_offset != slice->m_start) + { + Slice* t = _newSlice(); + if (!t) goto _bad; + t->m_pool = nullptr; + t->m_block = slice->m_block; + t->m_start = slice->m_start; + t->m_end = start_offset; +#ifdef DEBUG_CMEMPOOL + printf("-> subdivide left: %08x-%08x\n", t->m_start, t->m_end); +#endif + m_memMap.addBefore(slice, t); + m_freeList.insert(t, true); + slice->m_start = start_offset; + } + + if (end_offset != slice->m_end) + { + Slice* t = _newSlice(); + if (!t) goto _bad; + t->m_pool = nullptr; + t->m_block = slice->m_block; + t->m_start = end_offset; + t->m_end = slice->m_end; +#ifdef DEBUG_CMEMPOOL + printf("-> subdivide right: %08x-%08x\n", t->m_start, t->m_end); +#endif + m_memMap.addAfter(slice, t); + m_freeList.insert(t, true); + slice->m_end = end_offset; + } + + slice->m_pool = this; + return slice; + +_bad: + m_freeList.insert(slice, true); + return nullptr; +} + +void CMemPool::_destroy(Slice* slice) +{ + slice->m_pool = nullptr; + + Slice* left = m_memMap.prev(slice); + Slice* right = m_memMap.next(slice); + + if (left && left->canCoalesce(*slice)) + { + slice->m_start = left->m_start; + m_freeList.remove(left); + m_memMap.remove(left); + _deleteSlice(left); + } + + if (right && slice->canCoalesce(*right)) + { + slice->m_end = right->m_end; + m_freeList.remove(right); + m_memMap.remove(right); + _deleteSlice(right); + } + + m_freeList.insert(slice, true); +} diff --git a/graphics/deko3d/deko_examples/source/SampleFramework/CMemPool.h b/graphics/deko3d/deko_examples/source/SampleFramework/CMemPool.h new file mode 100644 index 0000000..978755c --- /dev/null +++ b/graphics/deko3d/deko_examples/source/SampleFramework/CMemPool.h @@ -0,0 +1,120 @@ +/* +** Sample Framework for deko3d Applications +** CMemPool.h: Pooled dynamic memory allocation manager class +*/ +#pragma once +#include "common.h" +#include "CIntrusiveList.h" +#include "CIntrusiveTree.h" + +class CMemPool +{ + dk::Device m_dev; + uint32_t m_flags; + uint32_t m_blockSize; + + struct Block + { + CIntrusiveListNode m_node; + dk::MemBlock m_obj; + void* m_cpuAddr; + DkGpuAddr m_gpuAddr; + + constexpr void* cpuOffset(uint32_t offset) const + { + return m_cpuAddr ? ((u8*)m_cpuAddr + offset) : nullptr; + } + + constexpr DkGpuAddr gpuOffset(uint32_t offset) const + { + return m_gpuAddr != DK_GPU_ADDR_INVALID ? (m_gpuAddr + offset) : DK_GPU_ADDR_INVALID; + } + }; + + CIntrusiveList m_blocks; + + struct Slice + { + CIntrusiveListNode m_node; + CIntrusiveTreeNode m_treenode; + CMemPool* m_pool; + Block* m_block; + uint32_t m_start; + uint32_t m_end; + + constexpr uint32_t getSize() const { return m_end - m_start; } + constexpr bool canCoalesce(Slice const& rhs) const { return m_pool == rhs.m_pool && m_block == rhs.m_block && m_end == rhs.m_start; } + + constexpr bool operator<(Slice const& rhs) const { return getSize() < rhs.getSize(); } + constexpr bool operator<(uint32_t rhs) const { return getSize() < rhs; } + }; + + friend constexpr bool operator<(uint32_t lhs, Slice const& rhs); + + CIntrusiveList m_memMap, m_sliceHeap; + CIntrusiveTree m_freeList; + + Slice* _newSlice(); + void _deleteSlice(Slice*); + + void _destroy(Slice* slice); + +public: + static constexpr uint32_t DefaultBlockSize = 0x800000; + class Handle + { + Slice* m_slice; + public: + constexpr Handle(Slice* slice = nullptr) : m_slice{slice} { } + constexpr operator bool() const { return m_slice != nullptr; } + constexpr operator Slice*() const { return m_slice; } + constexpr bool operator!() const { return !m_slice; } + constexpr bool operator==(Handle const& rhs) const { return m_slice == rhs.m_slice; } + constexpr bool operator!=(Handle const& rhs) const { return m_slice != rhs.m_slice; } + + void destroy() + { + if (m_slice) + { + m_slice->m_pool->_destroy(m_slice); + m_slice = nullptr; + } + } + + constexpr dk::MemBlock getMemBlock() const + { + return m_slice->m_block->m_obj; + } + + constexpr uint32_t getOffset() const + { + return m_slice->m_start; + } + + constexpr uint32_t getSize() const + { + return m_slice->getSize(); + } + + constexpr void* getCpuAddr() const + { + return m_slice->m_block->cpuOffset(m_slice->m_start); + } + + constexpr DkGpuAddr getGpuAddr() const + { + return m_slice->m_block->gpuOffset(m_slice->m_start); + } + }; + + CMemPool(dk::Device dev, uint32_t flags = DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached, uint32_t blockSize = DefaultBlockSize) : + m_dev{dev}, m_flags{flags}, m_blockSize{blockSize}, m_blocks{}, m_memMap{}, m_sliceHeap{}, m_freeList{} { } + ~CMemPool(); + + Handle allocate(uint32_t size, uint32_t alignment = DK_CMDMEM_ALIGNMENT); +}; + +constexpr bool operator<(uint32_t lhs, CMemPool::Slice const& rhs) +{ + return lhs < rhs.getSize(); +} diff --git a/graphics/deko3d/deko_examples/source/SampleFramework/CShader.cpp b/graphics/deko3d/deko_examples/source/SampleFramework/CShader.cpp new file mode 100644 index 0000000..6c5361c --- /dev/null +++ b/graphics/deko3d/deko_examples/source/SampleFramework/CShader.cpp @@ -0,0 +1,62 @@ +/* +** Sample Framework for deko3d Applications +** CShader.cpp: Utility class for loading shaders from the filesystem +*/ +#include "CShader.h" + +struct DkshHeader +{ + uint32_t magic; // DKSH_MAGIC + uint32_t header_sz; // sizeof(DkshHeader) + uint32_t control_sz; + uint32_t code_sz; + uint32_t programs_off; + uint32_t num_programs; +}; + +bool CShader::load(CMemPool& pool, const char* path) +{ + FILE* f; + DkshHeader hdr; + void* ctrlmem; + + m_codemem.destroy(); + + f = fopen(path, "rb"); + if (!f) return false; + + if (!fread(&hdr, sizeof(hdr), 1, f)) + goto _fail0; + + ctrlmem = malloc(hdr.control_sz); + if (!ctrlmem) + goto _fail0; + + rewind(f); + if (!fread(ctrlmem, hdr.control_sz, 1, f)) + goto _fail1; + + m_codemem = pool.allocate(hdr.code_sz, DK_SHADER_CODE_ALIGNMENT); + if (!m_codemem) + goto _fail1; + + if (!fread(m_codemem.getCpuAddr(), hdr.code_sz, 1, f)) + goto _fail2; + + dk::ShaderMaker{m_codemem.getMemBlock(), m_codemem.getOffset()} + .setControl(ctrlmem) + .setProgramId(0) + .initialize(m_shader); + + free(ctrlmem); + fclose(f); + return true; + +_fail2: + m_codemem.destroy(); +_fail1: + free(ctrlmem); +_fail0: + fclose(f); + return false; +} diff --git a/graphics/deko3d/deko_examples/source/SampleFramework/CShader.h b/graphics/deko3d/deko_examples/source/SampleFramework/CShader.h new file mode 100644 index 0000000..b39dfe0 --- /dev/null +++ b/graphics/deko3d/deko_examples/source/SampleFramework/CShader.h @@ -0,0 +1,31 @@ +/* +** Sample Framework for deko3d Applications +** CShader.h: Utility class for loading shaders from the filesystem +*/ +#pragma once +#include "common.h" +#include "CMemPool.h" + +class CShader +{ + dk::Shader m_shader; + CMemPool::Handle m_codemem; +public: + CShader() : m_shader{}, m_codemem{} { } + ~CShader() + { + m_codemem.destroy(); + } + + constexpr operator bool() const + { + return m_codemem; + } + + constexpr operator dk::Shader const*() const + { + return &m_shader; + } + + bool load(CMemPool& pool, const char* path); +}; diff --git a/graphics/deko3d/deko_examples/source/SampleFramework/FileLoader.cpp b/graphics/deko3d/deko_examples/source/SampleFramework/FileLoader.cpp new file mode 100644 index 0000000..a9651bf --- /dev/null +++ b/graphics/deko3d/deko_examples/source/SampleFramework/FileLoader.cpp @@ -0,0 +1,27 @@ +/* +** Sample Framework for deko3d Applications +** FileLoader.cpp: Helpers for loading data from the filesystem directly into GPU memory +*/ +#include "FileLoader.h" + +CMemPool::Handle LoadFile(CMemPool& pool, const char* path, uint32_t alignment) +{ + FILE *f = fopen(path, "rb"); + if (!f) return nullptr; + + fseek(f, 0, SEEK_END); + uint32_t fsize = ftell(f); + rewind(f); + + CMemPool::Handle mem = pool.allocate(fsize, alignment); + if (!mem) + { + fclose(f); + return nullptr; + } + + fread(mem.getCpuAddr(), fsize, 1, f); + fclose(f); + + return mem; +} diff --git a/graphics/deko3d/deko_examples/source/SampleFramework/FileLoader.h b/graphics/deko3d/deko_examples/source/SampleFramework/FileLoader.h new file mode 100644 index 0000000..3455c87 --- /dev/null +++ b/graphics/deko3d/deko_examples/source/SampleFramework/FileLoader.h @@ -0,0 +1,9 @@ +/* +** Sample Framework for deko3d Applications +** FileLoader.h: Helpers for loading data from the filesystem directly into GPU memory +*/ +#pragma once +#include "common.h" +#include "CMemPool.h" + +CMemPool::Handle LoadFile(CMemPool& pool, const char* path, uint32_t alignment = DK_CMDMEM_ALIGNMENT); diff --git a/graphics/deko3d/deko_examples/source/SampleFramework/LICENSE b/graphics/deko3d/deko_examples/source/SampleFramework/LICENSE new file mode 100644 index 0000000..183debc --- /dev/null +++ b/graphics/deko3d/deko_examples/source/SampleFramework/LICENSE @@ -0,0 +1,18 @@ +Copyright (C) 2020 fincs + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you + must not claim that you wrote the original software. If you use + this software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and + must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source + distribution. diff --git a/graphics/deko3d/deko_examples/source/SampleFramework/common.h b/graphics/deko3d/deko_examples/source/SampleFramework/common.h new file mode 100644 index 0000000..814e499 --- /dev/null +++ b/graphics/deko3d/deko_examples/source/SampleFramework/common.h @@ -0,0 +1,12 @@ +/* +** Sample Framework for deko3d Applications +** common.h: Common includes +*/ +#pragma once +#include +#include +#include + +#include + +#include diff --git a/graphics/deko3d/deko_examples/source/SampleFramework/startup.cpp b/graphics/deko3d/deko_examples/source/SampleFramework/startup.cpp new file mode 100644 index 0000000..fd2b522 --- /dev/null +++ b/graphics/deko3d/deko_examples/source/SampleFramework/startup.cpp @@ -0,0 +1,34 @@ +/* +** Sample Framework for deko3d Applications +** startup.cpp: Automatic initialization/deinitialization +*/ +#include "common.h" + +//#define DEBUG_NXLINK + +#ifdef DEBUG_NXLINK +static int nxlink_sock = -1; +#endif + +extern "C" void userAppInit(void) +{ + Result res = romfsInit(); + if (R_FAILED(res)) + fatalThrow(res); + +#ifdef DEBUG_NXLINK + socketInitializeDefault(); + nxlink_sock = nxlinkStdio(); +#endif +} + +extern "C" void userAppExit(void) +{ +#ifdef DEBUG_NXLINK + if (nxlink_sock != -1) + close(nxlink_sock); + socketExit(); +#endif + + romfsExit(); +} diff --git a/graphics/deko3d/deko_examples/source/basic_deferred_fsh.glsl b/graphics/deko3d/deko_examples/source/basic_deferred_fsh.glsl new file mode 100644 index 0000000..3a45131 --- /dev/null +++ b/graphics/deko3d/deko_examples/source/basic_deferred_fsh.glsl @@ -0,0 +1,15 @@ +#version 460 + +layout (location = 0) in vec3 inWorldPos; +layout (location = 1) in vec3 inNormal; + +layout (location = 0) out vec4 outAlbedo; +layout (location = 1) out vec4 outNormal; +layout (location = 2) out vec4 outViewDir; + +void main() +{ + outAlbedo = vec4(1.0, 1.0, 1.0, 1.0); + outNormal = vec4(normalize(inNormal), 0.0); + outViewDir = vec4(-inWorldPos, 0.0); +} diff --git a/graphics/deko3d/deko_examples/source/basic_lighting_fsh.glsl b/graphics/deko3d/deko_examples/source/basic_lighting_fsh.glsl new file mode 100644 index 0000000..e95b687 --- /dev/null +++ b/graphics/deko3d/deko_examples/source/basic_lighting_fsh.glsl @@ -0,0 +1,46 @@ +#version 460 + +layout (location = 0) in vec3 inWorldPos; +layout (location = 1) in vec3 inNormal; +layout (location = 0) out vec4 outColor; + +layout (std140, binding = 0) uniform Lighting +{ + vec4 lightPos; // if w=0 this is lightDir + vec3 ambient; + vec3 diffuse; + vec4 specular; // w is shininess +} u; + +void main() +{ + // Renormalize the normal after interpolation + vec3 normal = normalize(inNormal); + + // Calculate light direction (i.e. vector that points *towards* the light source) + vec3 lightDir; + if (u.lightPos.w != 0.0) + lightDir = normalize(u.lightPos.xyz - inWorldPos); + else + lightDir = -u.lightPos.xyz; + vec3 viewDir = normalize(-inWorldPos); + + // Calculate diffuse factor + float diffuse = max(0.0, dot(normal,lightDir)); + + // Calculate specular factor (Blinn-Phong) + vec3 halfwayDir = normalize(lightDir + viewDir); + float specular = pow(max(0.0, dot(normal,halfwayDir)), u.specular.w); + + // Calculate the color + vec3 color = + u.ambient + + u.diffuse*vec3(diffuse) + + u.specular.xyz*vec3(specular); + + // Reinhard tone mapping + vec3 mappedColor = color / (vec3(1.0) + color); + + // Output this color (no need to gamma adjust since the framebuffer is sRGB) + outColor = vec4(mappedColor, 1.0); +} diff --git a/graphics/deko3d/deko_examples/source/basic_vsh.glsl b/graphics/deko3d/deko_examples/source/basic_vsh.glsl new file mode 100644 index 0000000..b569486 --- /dev/null +++ b/graphics/deko3d/deko_examples/source/basic_vsh.glsl @@ -0,0 +1,12 @@ +#version 460 + +layout (location = 0) in vec3 inPos; +layout (location = 1) in vec4 inAttrib; + +layout (location = 0) out vec4 outAttrib; + +void main() +{ + gl_Position = vec4(inPos, 1.0); + outAttrib = inAttrib; +} diff --git a/graphics/deko3d/deko_examples/source/color_fsh.glsl b/graphics/deko3d/deko_examples/source/color_fsh.glsl new file mode 100644 index 0000000..4fb790c --- /dev/null +++ b/graphics/deko3d/deko_examples/source/color_fsh.glsl @@ -0,0 +1,9 @@ +#version 460 + +layout (location = 0) in vec3 inColor; +layout (location = 0) out vec4 outColor; + +void main() +{ + outColor = vec4(inColor, 1.0); +} diff --git a/graphics/deko3d/deko_examples/source/composition_fsh.glsl b/graphics/deko3d/deko_examples/source/composition_fsh.glsl new file mode 100644 index 0000000..741a493 --- /dev/null +++ b/graphics/deko3d/deko_examples/source/composition_fsh.glsl @@ -0,0 +1,53 @@ +#version 460 + +layout (location = 0) out vec4 outColor; + +layout (binding = 0) uniform sampler2D texAlbedo; +layout (binding = 1) uniform sampler2D texNormal; +layout (binding = 2) uniform sampler2D texViewDir; + +layout (std140, binding = 0) uniform Lighting +{ + vec4 lightPos; // if w=0 this is lightDir + vec3 ambient; + vec3 diffuse; + vec4 specular; // w is shininess +} u; + +void main() +{ + // Uncomment the coordinate reversion below to observe the effects of tiled corruption + ivec2 coord = /*textureSize(texAlbedo, 0) - ivec2(1,1) -*/ ivec2(gl_FragCoord.xy); + + // Retrieve values from the g-buffer + vec4 albedo = texelFetch(texAlbedo, coord, 0); + vec3 normal = texelFetch(texNormal, coord, 0).xyz; + vec3 viewDir = texelFetch(texViewDir, coord, 0).xyz; + + // Calculate light direction (i.e. vector that points *towards* the light source) + vec3 lightDir; + if (u.lightPos.w != 0.0) + lightDir = normalize(u.lightPos.xyz + viewDir); + else + lightDir = -u.lightPos.xyz; + viewDir = normalize(viewDir); + + // Calculate diffuse factor + float diffuse = max(0.0, dot(normal,lightDir)); + + // Calculate specular factor (Blinn-Phong) + vec3 halfwayDir = normalize(lightDir + viewDir); + float specular = pow(max(0.0, dot(normal,halfwayDir)), u.specular.w); + + // Calculate the color + vec3 color = + u.ambient + + albedo.rgb*u.diffuse*vec3(diffuse) + + u.specular.xyz*vec3(specular); + + // Reinhard tone mapping + vec3 mappedColor = albedo.a * color / (vec3(1.0) + color); + + // Output this color (no need to gamma adjust since the framebuffer is sRGB) + outColor = vec4(mappedColor, albedo.a); +} diff --git a/graphics/deko3d/deko_examples/source/composition_vsh.glsl b/graphics/deko3d/deko_examples/source/composition_vsh.glsl new file mode 100644 index 0000000..38bcc36 --- /dev/null +++ b/graphics/deko3d/deko_examples/source/composition_vsh.glsl @@ -0,0 +1,24 @@ +#version 460 + +/* +ID | gl_Position.xy +0 | -1.0 +1.0 +1 | -1.0 -1.0 +2 | +1.0 -1.0 +3 | +1.0 +1.0 +*/ + +void main() +{ + if ((gl_VertexID & 2) == 0) + gl_Position.x = -1.0; + else + gl_Position.x = +1.0; + + if (((gl_VertexID+1) & 2) == 0) + gl_Position.y = +1.0; + else + gl_Position.y = -1.0; + + gl_Position.zw = vec2(0.5, 1.0); +} diff --git a/graphics/deko3d/deko_examples/source/main.cpp b/graphics/deko3d/deko_examples/source/main.cpp new file mode 100644 index 0000000..2e68447 --- /dev/null +++ b/graphics/deko3d/deko_examples/source/main.cpp @@ -0,0 +1,137 @@ +/* +** deko3d Examples - Main Menu +*/ + +// Sample Framework headers +#include "SampleFramework/CApplication.h" + +// C++ standard library headers +#include + +void Example01(void); +void Example02(void); +void Example03(void); +void Example04(void); +void Example05(void); +void Example06(void); +void Example07(void); +void Example08(void); +void Example09(void); + +namespace +{ + using ExampleFunc = void(*)(void); + struct Example + { + ExampleFunc mainfunc; + const char* name; + }; + + constexpr std::array Examples = + { + Example{ Example01, "01: Simple Setup" }, + Example{ Example02, "02: Triangle" }, + Example{ Example03, "03: Cube" }, + Example{ Example04, "04: Textured Cube" }, + Example{ Example05, "05: Simple Tessellation" }, + Example{ Example06, "06: Simple Multisampling" }, + Example{ Example07, "07: Mesh Loading and Lighting (sRGB)" }, + Example{ Example08, "08: Deferred Shading (Multipass Rendering with Tiled Cache)" }, + Example{ Example09, "09: Simple Compute Shader (Geometry Generation)" }, + }; +} + +class CMainMenu final : public CApplication +{ + static constexpr unsigned EntriesPerScreen = 39; + static constexpr unsigned EntryPageLength = 10; + + int screenPos; + int selectPos; + + void renderMenu() + { + printf("\x1b[2J\n"); + printf(" deko3d Examples\n"); + printf(" Press PLUS(+) to exit; A to select an example to run\n"); + printf("\n"); + printf("--------------------------------------------------------------------------------"); + printf("\n"); + + for (unsigned i = 0; i < (Examples.size() - screenPos) && i < EntriesPerScreen; i ++) + { + unsigned id = screenPos+i; + printf(" %c %s\n", id==unsigned(selectPos) ? '*' : ' ', Examples[id].name); + } + } + + CMainMenu() : screenPos{}, selectPos{} + { + consoleInit(NULL); + renderMenu(); + } + + ~CMainMenu() + { + consoleExit(NULL); + } + + bool onFrame(u64 ns) override + { + int oldPos = selectPos; + hidScanInput(); + + u64 kDown = hidKeysDown(CONTROLLER_P1_AUTO); + if (kDown & KEY_PLUS) + { + selectPos = -1; + return false; + } + if (kDown & KEY_A) + return false; + if (kDown & KEY_UP) + selectPos -= 1; + if (kDown & KEY_DOWN) + selectPos += 1; + if (kDown & KEY_LEFT) + selectPos -= EntryPageLength; + if (kDown & KEY_RIGHT) + selectPos += EntryPageLength; + + if (selectPos < 0) + selectPos = 0; + if (unsigned(selectPos) >= Examples.size()) + selectPos = Examples.size()-1; + + if (selectPos != oldPos) + { + if (selectPos < screenPos) + screenPos = selectPos; + else if (selectPos >= screenPos + int(EntriesPerScreen)) + screenPos = selectPos - EntriesPerScreen + 1; + renderMenu(); + } + + consoleUpdate(NULL); + return true; + } + +public: + static ExampleFunc Display() + { + CMainMenu app; + app.run(); + return app.selectPos >= 0 ? Examples[app.selectPos].mainfunc : nullptr; + } +}; + +int main(int argc, char* argv[]) +{ + for (;;) + { + ExampleFunc func = CMainMenu::Display(); + if (!func) break; + func(); + } + return 0; +} diff --git a/graphics/deko3d/deko_examples/source/sinewave.glsl b/graphics/deko3d/deko_examples/source/sinewave.glsl new file mode 100644 index 0000000..174cd7b --- /dev/null +++ b/graphics/deko3d/deko_examples/source/sinewave.glsl @@ -0,0 +1,38 @@ +#version 460 + +layout (local_size_x = 32) in; + +struct Vertex +{ + vec4 position; + vec4 color; +}; + +layout (std140, binding = 0) uniform Params +{ + vec4 colorA; + vec4 colorB; + float offset; + float scale; +} u; + +layout (std430, binding = 0) buffer Output +{ + Vertex vertices[]; +} o; + +const float TAU = 6.2831853071795; + +void calcVertex(out Vertex vtx, float x) +{ + vtx.position = vec4(x * 2.0 - 1.0, u.scale * sin((u.offset + x)*TAU), 0.5, 1.0); + vtx.color = mix(u.colorA, u.colorB, x); +} + +void main() +{ + uint id = gl_GlobalInvocationID.x; + uint maxid = gl_WorkGroupSize.x * gl_NumWorkGroups.x - 1; + float x = float(id) / float(maxid); + calcVertex(o.vertices[id], x); +} diff --git a/graphics/deko3d/deko_examples/source/tess_simple_tcsh.glsl b/graphics/deko3d/deko_examples/source/tess_simple_tcsh.glsl new file mode 100644 index 0000000..44a822d --- /dev/null +++ b/graphics/deko3d/deko_examples/source/tess_simple_tcsh.glsl @@ -0,0 +1,21 @@ +#version 460 + +layout (vertices = 3) out; + +layout (location = 0) in vec4 inAttrib[]; + +layout (location = 0) out vec4 outAttrib[]; + +void main() +{ + if (gl_InvocationID == 0) + { + gl_TessLevelInner[0] = 5.0; // i.e. 2 concentric triangles with the center being a triangle + gl_TessLevelOuter[0] = 2.0; + gl_TessLevelOuter[1] = 3.0; + gl_TessLevelOuter[2] = 5.0; + } + + gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position; + outAttrib[gl_InvocationID] = inAttrib[gl_InvocationID]; +} diff --git a/graphics/deko3d/deko_examples/source/tess_simple_tesh.glsl b/graphics/deko3d/deko_examples/source/tess_simple_tesh.glsl new file mode 100644 index 0000000..117fff6 --- /dev/null +++ b/graphics/deko3d/deko_examples/source/tess_simple_tesh.glsl @@ -0,0 +1,21 @@ +#version 460 + +layout (triangles, equal_spacing, ccw) in; + +layout (location = 0) in vec4 inAttrib[]; + +layout (location = 0) out vec4 outAttrib; + +vec4 interpolate(in vec4 v0, in vec4 v1, in vec4 v2) +{ + vec4 a0 = gl_TessCoord.x * v0; + vec4 a1 = gl_TessCoord.y * v1; + vec4 a2 = gl_TessCoord.z * v2; + return a0 + a1 + a2; +} + +void main() +{ + gl_Position = interpolate(gl_in[0].gl_Position, gl_in[1].gl_Position, gl_in[2].gl_Position); + outAttrib = interpolate(inAttrib[0], inAttrib[1], inAttrib[2]); +} diff --git a/graphics/deko3d/deko_examples/source/texture_fsh.glsl b/graphics/deko3d/deko_examples/source/texture_fsh.glsl new file mode 100644 index 0000000..9092000 --- /dev/null +++ b/graphics/deko3d/deko_examples/source/texture_fsh.glsl @@ -0,0 +1,11 @@ +#version 460 + +layout (location = 0) in vec2 inTexCoord; +layout (location = 0) out vec4 outColor; + +layout (binding = 0) uniform sampler2D texture0; + +void main() +{ + outColor = texture(texture0, inTexCoord); +} diff --git a/graphics/deko3d/deko_examples/source/transform_normal_vsh.glsl b/graphics/deko3d/deko_examples/source/transform_normal_vsh.glsl new file mode 100644 index 0000000..0b7ac0f --- /dev/null +++ b/graphics/deko3d/deko_examples/source/transform_normal_vsh.glsl @@ -0,0 +1,28 @@ +#version 460 + +layout (location = 0) in vec3 inPos; +layout (location = 1) in vec3 inNormal; +layout (location = 2) in vec4 inAttrib; + +layout (location = 0) out vec3 outWorldPos; +layout (location = 1) out vec3 outNormal; +layout (location = 2) out vec4 outAttrib; + +layout (std140, binding = 0) uniform Transformation +{ + mat4 mdlvMtx; + mat4 projMtx; +} u; + +void main() +{ + vec4 worldPos = u.mdlvMtx * vec4(inPos, 1.0); + gl_Position = u.projMtx * worldPos; + + outWorldPos = worldPos.xyz; + + outNormal = normalize(mat3(u.mdlvMtx) * inNormal); + + // Pass through the user-defined attribute + outAttrib = inAttrib; +} diff --git a/graphics/deko3d/deko_examples/source/transform_vsh.glsl b/graphics/deko3d/deko_examples/source/transform_vsh.glsl new file mode 100644 index 0000000..e4310ef --- /dev/null +++ b/graphics/deko3d/deko_examples/source/transform_vsh.glsl @@ -0,0 +1,20 @@ +#version 460 + +layout (location = 0) in vec3 inPos; +layout (location = 1) in vec4 inAttrib; + +layout (location = 0) out vec4 outAttrib; + +layout (std140, binding = 0) uniform Transformation +{ + mat4 mdlvMtx; + mat4 projMtx; +} u; + +void main() +{ + vec4 pos = u.mdlvMtx * vec4(inPos, 1.0); + gl_Position = u.projMtx * pos; + + outAttrib = inAttrib; +}