Add basic deko3d C example (rendering a triangle)

This commit is contained in:
fincs 2020-02-07 23:36:51 +01:00
parent a34aba3b72
commit 554d75d499
No known key found for this signature in database
GPG Key ID: 62C7609ADA219C60
5 changed files with 530 additions and 0 deletions

1
.gitignore vendored
View File

@ -8,4 +8,5 @@ build
*.nsp *.nsp
*.npdm *.npdm
*.bz2 *.bz2
*.dksh
*~ *~

View File

@ -0,0 +1,271 @@
#---------------------------------------------------------------------------------
.SUFFIXES:
#---------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITPRO)),)
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/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):
# - <Project name>.jpg
# - icon.jpg
# - <libnx folder>/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):
# - <Project name>.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
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
#---------------------------------------------------------------------------------------

View File

@ -0,0 +1,9 @@
#version 460
layout (location = 0) in vec4 inColor;
layout (location = 0) out vec4 outColor;
void main()
{
outColor = inColor;
}

View File

@ -0,0 +1,226 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <switch.h>
#include <deko3d.h>
// Define the desired number of framebuffers
#define FB_NUM 2
// Define the desired framebuffer resolution (here we set it to 720p).
#define FB_WIDTH 1280
#define FB_HEIGHT 720
// Remove above and uncomment below for 1080p
//#define FB_WIDTH 1920
//#define FB_HEIGHT 1080
// Define the size of the memory block that will hold code
#define CODEMEMSIZE (64*1024)
// Define the size of the memory block that will hold command lists
#define CMDMEMSIZE (16*1024)
static DkDevice g_device;
static DkMemBlock g_framebufferMemBlock;
static DkImage g_framebuffers[FB_NUM];
static DkSwapchain g_swapchain;
static DkMemBlock g_codeMemBlock;
static uint32_t g_codeMemOffset;
static DkShader g_vertexShader;
static DkShader g_fragmentShader;
static DkMemBlock g_cmdbufMemBlock;
static DkCmdBuf g_cmdbuf;
static DkCmdList g_cmdsBindFramebuffer[FB_NUM];
static DkCmdList g_cmdsRender;
static DkQueue g_renderQueue;
// Simple function for loading a shader from the filesystem
static void loadShader(DkShader* pShader, const char* path)
{
// Open the file, and retrieve its size
FILE* f = fopen(path, "rb");
fseek(f, 0, SEEK_END);
uint32_t size = ftell(f);
rewind(f);
// Look for a spot in the code memory block for loading this shader. Note that
// we are just using a simple incremental offset; this isn't a general purpose
// allocation algorithm.
uint32_t codeOffset = g_codeMemOffset;
g_codeMemOffset += (size + DK_SHADER_CODE_ALIGNMENT - 1) &~ (DK_SHADER_CODE_ALIGNMENT - 1);
// Read the file into memory, and close the file
fread((uint8_t*)dkMemBlockGetCpuAddr(g_codeMemBlock) + codeOffset, size, 1, f);
fclose(f);
// Initialize the user provided shader object with the code we've just loaded
DkShaderMaker shaderMaker;
dkShaderMakerDefaults(&shaderMaker, g_codeMemBlock, codeOffset);
dkShaderInitialize(pShader, &shaderMaker);
}
// This function creates all the necessary graphical resources.
static void graphicsInitialize(void)
{
// Create the device, which is the root object
DkDeviceMaker deviceMaker;
dkDeviceMakerDefaults(&deviceMaker);
g_device = dkDeviceCreate(&deviceMaker);
// Calculate layout for the framebuffers
DkImageLayoutMaker imageLayoutMaker;
dkImageLayoutMakerDefaults(&imageLayoutMaker, g_device);
imageLayoutMaker.flags = DkImageFlags_UsageRender | DkImageFlags_UsagePresent | DkImageFlags_HwCompression;
imageLayoutMaker.format = DkImageFormat_RGBA8_Unorm;
imageLayoutMaker.dimensions[0] = FB_WIDTH;
imageLayoutMaker.dimensions[1] = FB_HEIGHT;
// Calculate layout for the framebuffers
DkImageLayout framebufferLayout;
dkImageLayoutInitialize(&framebufferLayout, &imageLayoutMaker);
// Retrieve necessary size and alignment for the framebuffers
uint32_t framebufferSize = dkImageLayoutGetSize(&framebufferLayout);
uint32_t framebufferAlign = dkImageLayoutGetAlignment(&framebufferLayout);
framebufferSize = (framebufferSize + framebufferAlign - 1) &~ (framebufferAlign - 1);
// Create a memory block that will host the framebuffers
DkMemBlockMaker memBlockMaker;
dkMemBlockMakerDefaults(&memBlockMaker, g_device, FB_NUM*framebufferSize);
memBlockMaker.flags = DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image;
g_framebufferMemBlock = dkMemBlockCreate(&memBlockMaker);
// Initialize the framebuffers with the layout and backing memory we've just created
DkImage const* swapchainImages[FB_NUM];
for (unsigned i = 0; i < FB_NUM; i ++)
{
swapchainImages[i] = &g_framebuffers[i];
dkImageInitialize(&g_framebuffers[i], &framebufferLayout, g_framebufferMemBlock, i*framebufferSize);
}
// Create a swapchain out of the framebuffers we've just initialized
DkSwapchainMaker swapchainMaker;
dkSwapchainMakerDefaults(&swapchainMaker, g_device, nwindowGetDefault(), swapchainImages, FB_NUM);
g_swapchain = dkSwapchainCreate(&swapchainMaker);
// Create a memory block onto which we will load shader code
dkMemBlockMakerDefaults(&memBlockMaker, g_device, CODEMEMSIZE);
memBlockMaker.flags = DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached | DkMemBlockFlags_Code;
g_codeMemBlock = dkMemBlockCreate(&memBlockMaker);
g_codeMemOffset = 0;
// Load our shaders (both vertex and fragment)
loadShader(&g_vertexShader, "romfs:/shaders/triangle_vsh.dksh");
loadShader(&g_fragmentShader, "romfs:/shaders/color_fsh.dksh");
// Create a memory block which will be used for recording command lists using a command buffer
dkMemBlockMakerDefaults(&memBlockMaker, g_device, CMDMEMSIZE);
memBlockMaker.flags = DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached;
g_cmdbufMemBlock = dkMemBlockCreate(&memBlockMaker);
// Create a command buffer object
DkCmdBufMaker cmdbufMaker;
dkCmdBufMakerDefaults(&cmdbufMaker, g_device);
g_cmdbuf = dkCmdBufCreate(&cmdbufMaker);
// Feed our memory to the command buffer so that we can start recording commands
dkCmdBufAddMemory(g_cmdbuf, g_cmdbufMemBlock, 0, CMDMEMSIZE);
// Generate a command list for each framebuffer, which will bind each of them as a render target
for (unsigned i = 0; i < FB_NUM; i ++)
{
DkImageView imageView;
dkImageViewDefaults(&imageView, &g_framebuffers[i]);
dkCmdBufBindRenderTarget(g_cmdbuf, &imageView, NULL);
g_cmdsBindFramebuffer[i] = dkCmdBufFinishList(g_cmdbuf);
}
// Declare structs that will be used for binding state
DkViewport viewport = { 0.0f, 0.0f, (float)FB_WIDTH, (float)FB_HEIGHT, 0.0f, 1.0f };
DkScissor scissor = { 0, 0, FB_WIDTH, FB_HEIGHT };
DkShader const* shaders[] = { &g_vertexShader, &g_fragmentShader };
DkRasterizerState rasterizerState;
DkColorState colorState;
DkColorWriteState colorWriteState;
// Initialize state structs with the deko3d defaults
dkRasterizerStateDefaults(&rasterizerState);
dkColorStateDefaults(&colorState);
dkColorWriteStateDefaults(&colorWriteState);
// Generate the main rendering command list
dkCmdBufSetViewports(g_cmdbuf, 0, &viewport, 1);
dkCmdBufSetScissors(g_cmdbuf, 0, &scissor, 1);
dkCmdBufClearColorFloat(g_cmdbuf, 0, DkColorMask_RGBA, 0.125f, 0.294f, 0.478f, 1.0f);
dkCmdBufBindShaders(g_cmdbuf, DkStageFlag_GraphicsMask, shaders, sizeof(shaders)/sizeof(shaders[0]));
dkCmdBufBindRasterizerState(g_cmdbuf, &rasterizerState);
dkCmdBufBindColorState(g_cmdbuf, &colorState);
dkCmdBufBindColorWriteState(g_cmdbuf, &colorWriteState);
dkCmdBufDraw(g_cmdbuf, DkPrimitive_Triangles, 3, 1, 0, 0);
g_cmdsRender = dkCmdBufFinishList(g_cmdbuf);
// Create a queue, to which we will submit our command lists
DkQueueMaker queueMaker;
dkQueueMakerDefaults(&queueMaker, g_device);
queueMaker.flags = DkQueueFlags_Graphics;
g_renderQueue = dkQueueCreate(&queueMaker);
}
// This function is to be called at each frame, and it is in charge of rendering.
static void graphicsUpdate(void)
{
// Acquire a framebuffer from the swapchain (and wait for it to be available)
int slot = dkQueueAcquireImage(g_renderQueue, g_swapchain);
// Run the command list that binds said framebuffer as a render target
dkQueueSubmitCommands(g_renderQueue, g_cmdsBindFramebuffer[slot]);
// Run the main rendering command list
dkQueueSubmitCommands(g_renderQueue, g_cmdsRender);
// Now that we are done rendering, present it to the screen
dkQueuePresentImage(g_renderQueue, g_swapchain, slot);
}
// This function destroys the graphical resources created by graphicsInitialize.
static void graphicsExit(void)
{
// Make sure the rendering queue is idle before destroying anything
dkQueueWaitIdle(g_renderQueue);
// Destroy all the resources we've created
dkQueueDestroy(g_renderQueue);
dkCmdBufDestroy(g_cmdbuf);
dkMemBlockDestroy(g_cmdbufMemBlock);
dkMemBlockDestroy(g_codeMemBlock);
dkSwapchainDestroy(g_swapchain);
dkMemBlockDestroy(g_framebufferMemBlock);
dkDeviceDestroy(g_device);
}
// Main entrypoint
int main(int argc, char* argv[])
{
romfsInit();
graphicsInitialize();
while (appletMainLoop())
{
hidScanInput();
u64 kDown = hidKeysDown(CONTROLLER_P1_AUTO);
if (kDown & KEY_PLUS)
break; // break in order to return to hbmenu
graphicsUpdate();
}
graphicsExit();
romfsExit();
return 0;
}

View File

@ -0,0 +1,23 @@
#version 460
// Hardcoded array of vertex positions for our triangle
const vec4 positions[3] = vec4[](
vec4( 0.0, +1.0, 0.0, 1.0),
vec4(-1.0, -1.0, 0.0, 1.0),
vec4(+1.0, -1.0, 0.0, 1.0)
);
// Hardcoded array of vertex colors for our triangle
const vec4 colors[3] = vec4[](
vec4(1.0, 0.0, 0.0, 1.0),
vec4(0.0, 1.0, 0.0, 1.0),
vec4(0.0, 0.0, 1.0, 1.0)
);
layout (location = 0) out vec4 outColor;
void main()
{
gl_Position = positions[gl_VertexID];
outColor = colors[gl_VertexID];
}