Initial work on N3DS accelerated renderer. Just a bunch of code right now - requires a lot of fixes (vertex coordinates, shader, etc.) to get anywhere.

This commit is contained in:
Adrian Siekierka 2022-09-18 22:24:52 +02:00 committed by Cameron Cawley
parent c99670a6c9
commit b48099b625
10 changed files with 1199 additions and 4 deletions

View File

@ -2854,6 +2854,9 @@ elseif(N3DS)
file(GLOB N3DS_MAIN_SOURCES ${SDL2_SOURCE_DIR}/src/main/n3ds/*.c)
set(SDLMAIN_SOURCES ${SDLMAIN_SOURCES} ${N3DS_MAIN_SOURCES})
file(GLOB N3DS_CORE_SOURCES ${SDL2_SOURCE_DIR}/src/core/n3ds/*.c)
list(APPEND SOURCE_FILES ${N3DS_CORE_SOURCES})
if(SDL_AUDIO)
set(SDL_AUDIO_DRIVER_N3DS 1)
file(GLOB N3DS_AUDIO_SOURCES ${SDL2_SOURCE_DIR}/src/audio/n3ds/*.c)
@ -2907,8 +2910,10 @@ elseif(N3DS)
if(SDL_VIDEO)
set(SDL_VIDEO_DRIVER_N3DS 1)
file(GLOB N3DS_VIDEO_SOURCES ${SDL2_SOURCE_DIR}/src/video/n3ds/*.c)
set(SDL_VIDEO_RENDER_N3DS 1)
file(GLOB N3DS_VIDEO_SOURCES ${SDL2_SOURCE_DIR}/src/video/n3ds/*.c ${SDL2_SOURCE_DIR}/src/render/n3ds/*.c)
list(APPEND SOURCE_FILES ${N3DS_VIDEO_SOURCES})
set(SDL_VIDEO_OPENGL 0)
set(HAVE_SDL_VIDEO TRUE)
endif()

View File

@ -464,6 +464,7 @@
#cmakedefine SDL_VIDEO_RENDER_OGL_ES2 @SDL_VIDEO_RENDER_OGL_ES2@
#cmakedefine SDL_VIDEO_RENDER_DIRECTFB @SDL_VIDEO_RENDER_DIRECTFB@
#cmakedefine SDL_VIDEO_RENDER_METAL @SDL_VIDEO_RENDER_METAL@
#cmakedefine SDL_VIDEO_RENDER_N3DS @SDL_VIDEO_RENDER_N3DS@
#cmakedefine SDL_VIDEO_RENDER_VITA_GXM @SDL_VIDEO_RENDER_VITA_GXM@
#cmakedefine SDL_VIDEO_RENDER_PS2 @SDL_VIDEO_RENDER_PS2@
#cmakedefine SDL_VIDEO_RENDER_PSP @SDL_VIDEO_RENDER_PSP@

53
src/core/n3ds/SDL_n3ds.c Normal file
View File

@ -0,0 +1,53 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
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.
*/
#include <3ds.h>
#include "../../SDL_internal.h"
#include "3ds/allocator/linear.h"
#include "SDL_stdinc.h"
#ifdef __3DS__
void* N3DS_linearRealloc(void* mem, size_t size) {
/* FIXME: Remove this once libctru implements linearRealloc(). */
if (mem == NULL) {
return linearAlloc(size);
} else if (linearGetSize(mem) <= size) {
return mem;
} else {
void *newMem = linearAlloc(size);
if (newMem != NULL) {
SDL_memcpy(newMem, mem, size);
linearFree(mem);
return newMem;
} else {
return NULL;
}
}
}
void N3DS_linearFree(void* mem) {
linearFree(mem);
}
#endif /* __3DS__ */
/* vi: set ts=4 sw=4 expandtab: */

41
src/core/n3ds/SDL_n3ds.h Normal file
View File

@ -0,0 +1,41 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
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.
*/
#include "../../SDL_internal.h"
#include "SDL_system.h"
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
/* *INDENT-OFF* */
extern "C" {
/* *INDENT-ON* */
#endif
extern void* N3DS_linearRealloc(void* mem, size_t size);
extern void N3DS_linearFree(void* mem);
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
/* *INDENT-OFF* */
}
/* *INDENT-ON* */
#endif
/* vi: set ts=4 sw=4 expandtab: */

View File

@ -33,6 +33,10 @@
#include "../core/android/SDL_android.h"
#endif
#if defined(__3DS__)
# include "../core/n3ds/SDL_n3ds.h"
#endif
/* as a courtesy to iOS apps, we don't try to draw when in the background, as
that will crash the app. However, these apps _should_ have used
SDL_AddEventWatch to catch SDL_APP_WILLENTERBACKGROUND events and stopped
@ -122,6 +126,9 @@ static const SDL_RenderDriver *render_drivers[] = {
#if SDL_VIDEO_RENDER_DIRECTFB
&DirectFB_RenderDriver,
#endif
#if SDL_VIDEO_RENDER_N3DS
&N3DS_RenderDriver,
#endif
#if SDL_VIDEO_RENDER_PS2
&PS2_RenderDriver,
#endif
@ -306,7 +313,12 @@ void *SDL_AllocateRenderVertices(SDL_Renderer *renderer, const size_t numbytes,
newsize *= 2;
}
#ifdef __3DS__
/* The 3DS GPU expects vertex data to be linear in physical memory. */
ptr = N3DS_linearRealloc(renderer->vertex_data, newsize);
#else
ptr = SDL_realloc(renderer->vertex_data, newsize);
#endif
if (!ptr) {
SDL_OutOfMemory();
@ -906,7 +918,7 @@ static SDL_INLINE void VerifyDrawQueueFunctions(const SDL_Renderer *renderer)
have to check that they aren't NULL over and over. */
SDL_assert(renderer->QueueSetViewport != NULL);
SDL_assert(renderer->QueueSetDrawColor != NULL);
SDL_assert(renderer->QueueDrawPoints != NULL);
SDL_assert(renderer->QueueDrawPoints != NULL || renderer->QueueGeometry != NULL);
SDL_assert(renderer->QueueDrawLines != NULL || renderer->QueueGeometry != NULL);
SDL_assert(renderer->QueueFillRects != NULL || renderer->QueueGeometry != NULL);
SDL_assert(renderer->QueueCopy != NULL || renderer->QueueGeometry != NULL);
@ -2929,7 +2941,7 @@ static int RenderDrawLineBresenham(SDL_Renderer *renderer, int x1, int y1, int x
}
}
if (renderer->scale.x != 1.0f || renderer->scale.y != 1.0f) {
if (renderer->scale.x != 1.0f || renderer->scale.y != 1.0f || renderer->point_method == SDL_RENDERPOINTMETHOD_GEOMETRY) {
retval = RenderDrawPointsWithRectsF(renderer, points, numpixels);
} else {
retval = QueueCmdDrawPoints(renderer, points, numpixels);
@ -4380,7 +4392,11 @@ void SDL_DestroyRendererWithoutFreeing(SDL_Renderer *renderer)
cmd = next;
}
#ifdef __3DS__
N3DS_linearFree(renderer->vertex_data);
#else
SDL_free(renderer->vertex_data);
#endif
/* Free existing textures for this renderer */
while (renderer->textures) {

View File

@ -133,6 +133,12 @@ typedef struct SDL_VertexSolid
SDL_Color color;
} SDL_VertexSolid;
typedef enum
{
SDL_RENDERPOINTMETHOD_POINTS,
SDL_RENDERPOINTMETHOD_GEOMETRY,
} SDL_RenderPointMethod;
typedef enum
{
SDL_RENDERLINEMETHOD_POINTS,
@ -246,6 +252,9 @@ struct SDL_Renderer
/* Whether or not to scale relative mouse motion */
SDL_bool relative_scaling;
/* The method of drawing points */
SDL_RenderPointMethod point_method;
/* The method of drawing lines */
SDL_RenderLineMethod line_method;
@ -305,6 +314,7 @@ extern SDL_RenderDriver GLES2_RenderDriver;
extern SDL_RenderDriver GLES_RenderDriver;
extern SDL_RenderDriver DirectFB_RenderDriver;
extern SDL_RenderDriver METAL_RenderDriver;
extern SDL_RenderDriver N3DS_RenderDriver;
extern SDL_RenderDriver PS2_RenderDriver;
extern SDL_RenderDriver PSP_RenderDriver;
extern SDL_RenderDriver SW_RenderDriver;

View File

@ -0,0 +1,966 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
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.
*/
#include "../../SDL_internal.h"
#include "3ds/gfx.h"
#include "3ds/services/gspgpu.h"
#include "SDL_error.h"
#include "SDL_pixels.h"
#include "SDL_render.h"
#include "SDL_stdinc.h"
#include "SDL_video.h"
#if SDL_VIDEO_RENDER_N3DS
#include "SDL_hints.h"
#include "../SDL_sysrender.h"
#include <stdbool.h>
#include <3ds.h>
#include <citro3d.h>
#include "SDL_render_n3ds_shaders.h"
/* N3DS renderer implementation, derived from the PSP implementation */
static int
PixelFormatToN3DSGPU(Uint32 format)
{
switch (format) {
case SDL_PIXELFORMAT_RGBA8888:
return GPU_RGBA8;
case SDL_PIXELFORMAT_RGB888:
return GPU_RGB8;
case SDL_PIXELFORMAT_RGBA5551:
return GPU_RGBA5551;
case SDL_PIXELFORMAT_RGB565:
return GPU_RGB565;
case SDL_PIXELFORMAT_RGBA4444:
return GPU_RGBA4;
default:
return GPU_RGBA8;
}
}
#define COL5650(r,g,b,a) ((b>>3) | ((g>>2)<<5) | ((r>>3)<<11))
#define COL5551(r,g,b,a) (((b>>3)<<1) | ((g>>3)<<6) | ((r>>3)<<11) | (a>0?1:0))
#define COL4444(r,g,b,a) ((a>>4) | ((b>>4)<<4) | ((g>>4)<<8) | ((r>>4)<<12))
#define COL8888(r,g,b,a) ((a) | ((b)<<8) | ((g)<<16) | ((r)<<24))
#define COL888(r,g,b) ((b) | ((g)<<16) | ((r)<<24))
typedef struct
{
C3D_Tex texture;
C3D_RenderTarget* renderTarget;
C3D_Mtx renderProjMtx;
unsigned int width; /**< Image width. */
unsigned int height; /**< Image height. */
unsigned int pitch;
unsigned int size;
/**
* The 3DS GPU requires all textures to be *swizzled* before use.
*
* For textures considered STREAMING, we keep an unswizzled buffer in memory
* at all times. For textures considered STATIC or TARGET, we generate an
* unswizzled memory buffer on demand - this saves memory usage, but slows
* down updates.
*
* To save on memory usage, we align the unswizzled buffer's width/height
* to a multiple of 8, as opposed to the next power of two. The 3DS GPU can
* deal with that.
*/
void* unswizzledBuffer;
unsigned int unswizzledWidth;
unsigned int unswizzledHeight;
unsigned int unswizzledPitch;
unsigned int unswizzledSize;
} N3DS_TextureData;
typedef struct
{
SDL_BlendMode mode;
SDL_Texture* texture;
} N3DS_BlendState;
typedef struct
{
C3D_RenderTarget* renderTarget;
C3D_Mtx renderProjMtx;
SDL_Texture* boundTarget; /**< currently bound rendertarget */
SDL_bool initialized; /**< is driver initialized */
unsigned int psm; /**< format of the display buffers */
unsigned int bpp; /**< bits per pixel of the main display */
SDL_bool vsync; /**< wether we do vsync */
N3DS_BlendState blendState; /**< current blend mode */
C3D_TexEnv envTex;
C3D_TexEnv envNoTex;
DVLB_s *dvlb;
shaderProgram_s shaderProgram;
int projMtxShaderLoc;
} N3DS_RenderData;
typedef struct
{
float x, y;
SDL_Color col;
float u, v;
} VertVCT;
#define PI 3.14159265358979f
#define radToDeg(x) ((x)*180.f/PI)
#define degToRad(x) ((x)*PI/180.f)
static inline void
Swap(float *a, float *b)
{
float n=*a;
*a = *b;
*b = n;
}
static inline int
InVram(void* data)
{
return data < (void*)0x20000000;
}
static int
TextureNextPow2(unsigned int w)
{
if (w < 8)
return 8;
w -= 1;
w |= w >> 1;
w |= w >> 2;
w |= w >> 4;
w |= w >> 8;
return w + 1;
}
static int
TextureAlign8(unsigned int w) {
return (w + 7) & (~7);
}
static void
TextureActivate(SDL_Texture *texture)
{
N3DS_TextureData *N3DS_texture = (N3DS_TextureData *) texture->driverdata;
C3D_TexBind(0, &N3DS_texture->texture);
}
static void
N3DS_WindowEvent(SDL_Renderer * renderer, const SDL_WindowEvent *event)
{
}
static int
N3DS_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture)
{
N3DS_TextureData* N3DS_texture = (N3DS_TextureData*) SDL_calloc(1, sizeof(*N3DS_texture));
bool initialized = SDL_FALSE;
if(!N3DS_texture)
return SDL_OutOfMemory();
N3DS_texture->width = texture->w;
N3DS_texture->height = texture->h;
initialized = C3D_TexInitVRAM(&N3DS_texture->texture,
TextureNextPow2(texture->w),
TextureNextPow2(texture->h),
PixelFormatToN3DSGPU(texture->format));
if (!initialized) {
initialized = C3D_TexInit(&N3DS_texture->texture,
TextureNextPow2(texture->w),
TextureNextPow2(texture->h),
PixelFormatToN3DSGPU(texture->format));
}
if (!initialized) {
SDL_free(N3DS_texture);
return SDL_OutOfMemory();
}
N3DS_texture->pitch = N3DS_texture->texture.width * SDL_BYTESPERPIXEL(texture->format);
N3DS_texture->size = N3DS_texture->texture.height * N3DS_texture->pitch;
N3DS_texture->unswizzledWidth = TextureAlign8(texture->w);
N3DS_texture->unswizzledHeight = TextureAlign8(texture->h);
N3DS_texture->unswizzledPitch = N3DS_texture->unswizzledWidth * SDL_BYTESPERPIXEL(texture->format);
N3DS_texture->unswizzledSize = N3DS_texture->unswizzledHeight * N3DS_texture->unswizzledPitch;
if (texture->access == SDL_TEXTUREACCESS_TARGET) {
N3DS_texture->renderTarget = C3D_RenderTargetCreateFromTex(&N3DS_texture->texture, GPU_TEXFACE_2D, 0, GPU_RB_DEPTH16);
if (N3DS_texture->renderTarget == NULL) {
SDL_free(N3DS_texture);
return SDL_OutOfMemory();
}
Mtx_Ortho(&N3DS_texture->renderProjMtx, 0.0, N3DS_texture->texture.width, 0.0, N3DS_texture->texture.height, -1.0, 1.0, true);
} else if (texture->access == SDL_TEXTUREACCESS_STREAMING) {
N3DS_texture->unswizzledBuffer = linearAlloc(N3DS_texture->unswizzledSize);
}
texture->driverdata = N3DS_texture;
return 0;
}
static int
N3DS_LockTexture(SDL_Renderer * renderer, SDL_Texture * texture,
const SDL_Rect * rect, void **pixels, int *pitch);
static void
N3DS_UnlockTexture(SDL_Renderer * renderer, SDL_Texture * texture);
static int
N3DS_UpdateTexture(SDL_Renderer * renderer, SDL_Texture * texture,
const SDL_Rect * rect, const void *pixels, int pitch)
{
N3DS_TextureData *N3DS_texture = (N3DS_TextureData *) texture->driverdata;
const Uint8 *src;
Uint8 *dst;
int row, length,dpitch;
src = pixels;
if (texture->access != SDL_TEXTUREACCESS_STREAMING) {
N3DS_texture->unswizzledBuffer = linearAlloc(N3DS_texture->unswizzledSize);
if (N3DS_texture->unswizzledBuffer == NULL) {
return SDL_OutOfMemory();
}
}
N3DS_LockTexture(renderer, texture, rect,(void **)&dst, &dpitch);
length = rect->w * SDL_BYTESPERPIXEL(texture->format);
if (length == pitch && length == dpitch) {
SDL_memcpy(dst, src, length*rect->h);
} else {
for (row = 0; row < rect->h; ++row) {
SDL_memcpy(dst, src, length);
src += pitch;
dst += dpitch;
}
}
N3DS_UnlockTexture(renderer, texture);
if (texture->access != SDL_TEXTUREACCESS_STREAMING) {
linearFree(N3DS_texture->unswizzledBuffer);
}
return 0;
}
static int
N3DS_LockTexture(SDL_Renderer * renderer, SDL_Texture * texture,
const SDL_Rect * rect, void **pixels, int *pitch)
{
N3DS_TextureData *N3DS_texture = (N3DS_TextureData *) texture->driverdata;
*pixels =
(void *) ((Uint8 *) N3DS_texture->unswizzledBuffer + rect->y * N3DS_texture->unswizzledPitch +
rect->x * SDL_BYTESPERPIXEL(texture->format));
*pitch = N3DS_texture->unswizzledPitch;
return 0;
}
static void
N3DS_UnlockTexture(SDL_Renderer * renderer, SDL_Texture * texture)
{
N3DS_TextureData *N3DS_texture = (N3DS_TextureData *) texture->driverdata;
/* We do whole texture updates, at least for now */
GSPGPU_FlushDataCache(N3DS_texture->unswizzledBuffer, N3DS_texture->unswizzledSize);
C3D_SyncDisplayTransfer(
N3DS_texture->unswizzledBuffer,
GX_BUFFER_DIM(N3DS_texture->unswizzledWidth, N3DS_texture->unswizzledHeight),
N3DS_texture->texture.data,
GX_BUFFER_DIM(N3DS_texture->texture.width, N3DS_texture->texture.height),
GX_TRANSFER_FLIP_VERT(0) | GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_RAW_COPY(0) |
GX_TRANSFER_IN_FORMAT(N3DS_texture->texture.fmt) | GX_TRANSFER_OUT_FORMAT(N3DS_texture->texture.fmt) |
GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_NO)
);
}
static void
N3DS_SetTextureScaleMode(SDL_Renderer * renderer, SDL_Texture * texture, SDL_ScaleMode scaleMode)
{
N3DS_TextureData *N3DS_texture = (N3DS_TextureData *) texture->driverdata;
GPU_TEXTURE_FILTER_PARAM filter = (scaleMode == SDL_ScaleModeNearest) ? GPU_NEAREST : GPU_LINEAR;
C3D_TexSetFilter(&N3DS_texture->texture, filter, filter);
}
static int
N3DS_SetRenderTarget(SDL_Renderer * renderer, SDL_Texture * texture)
{
N3DS_RenderData *data = renderer->driverdata;
data->boundTarget = texture;
if (texture == NULL) {
if (!C3D_FrameDrawOn(data->renderTarget)) {
return SDL_Unsupported();
}
C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, data->projMtxShaderLoc, &data->renderProjMtx);
} else {
N3DS_TextureData *N3DS_texture = (N3DS_TextureData *) texture->driverdata;
if (N3DS_texture->renderTarget != NULL) {
if (!C3D_FrameDrawOn(N3DS_texture->renderTarget)) {
return SDL_Unsupported();
}
C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, data->projMtxShaderLoc, &N3DS_texture->renderProjMtx);
} else {
return SDL_Unsupported();
}
}
return 0;
}
static int
N3DS_QueueSetViewport(SDL_Renderer * renderer, SDL_RenderCommand *cmd)
{
return 0; /* nothing to do in this backend. */
}
static int
N3DS_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Texture *texture,
const float *xy, int xy_stride, const SDL_Color *color, int color_stride, const float *uv, int uv_stride,
int num_vertices, const void *indices, int num_indices, int size_indices,
float scale_x, float scale_y)
{
int i;
int count = indices ? num_indices : num_vertices;
N3DS_TextureData *N3DS_texture = (N3DS_TextureData *) texture->driverdata;
VertVCT *verts;
cmd->data.draw.count = count;
size_indices = indices ? size_indices : 0;
verts = (VertVCT *) SDL_AllocateRenderVertices(renderer, count * sizeof (VertVCT), 0, &cmd->data.draw.first);
if (!verts) {
return -1;
}
if (texture == NULL) {
for (i = 0; i < count; i++) {
int j;
float *xy_;
SDL_Color col_;
if (size_indices == 4) {
j = ((const Uint32 *)indices)[i];
} else if (size_indices == 2) {
j = ((const Uint16 *)indices)[i];
} else if (size_indices == 1) {
j = ((const Uint8 *)indices)[i];
} else {
j = i;
}
xy_ = (float *)((char*)xy + j * xy_stride);
col_ = *(SDL_Color *)((char*)color + j * color_stride);
verts->x = xy_[0] * scale_x;
verts->y = xy_[1] * scale_y;
verts->col = col_;
verts++;
}
} else {
for (i = 0; i < count; i++) {
int j;
float *xy_;
SDL_Color col_;
float *uv_;
if (size_indices == 4) {
j = ((const Uint32 *)indices)[i];
} else if (size_indices == 2) {
j = ((const Uint16 *)indices)[i];
} else if (size_indices == 1) {
j = ((const Uint8 *)indices)[i];
} else {
j = i;
}
xy_ = (float *)((char*)xy + j * xy_stride);
col_ = *(SDL_Color *)((char*)color + j * color_stride);
uv_ = (float *)((char*)uv + j * uv_stride);
verts->x = xy_[0] * scale_x;
verts->y = xy_[1] * scale_y;
verts->col = col_;
verts->u = uv_[0] * N3DS_texture->texture.width;
verts->v = uv_[1] * N3DS_texture->texture.height;
verts++;
}
}
return 0;
}
static int
N3DS_QueueFillRects(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FRect * rects, int count)
{
SDL_Color color = *((SDL_Color*) &(cmd->data.draw.r));
VertVCT *verts = (VertVCT *) SDL_AllocateRenderVertices(renderer, count * 2 * sizeof (VertVCT), 4, &cmd->data.draw.first);
int i;
if (!verts) {
return -1;
}
cmd->data.draw.count = count * 4;
for (i = 0; i < count; i++, rects++) {
verts->x = rects->x;
verts->y = rects->y;
verts->col = color;
verts++;
verts->x = rects->x + rects->w;
verts->y = rects->y;
verts->col = color;
verts++;
verts->x = rects->x;
verts->y = rects->y + rects->h;
verts->col = color;
verts++;
verts->x = rects->x + rects->w;
verts->y = rects->y + rects->h;
verts->col = color;
verts++;
}
return 0;
}
static int
N3DS_QueueCopy(SDL_Renderer * renderer, SDL_RenderCommand *cmd, SDL_Texture * texture,
const SDL_Rect * srcrect, const SDL_FRect * dstrect)
{
SDL_Color color = *((SDL_Color*) &(cmd->data.draw.r));
VertVCT *verts;
const float x = dstrect->x;
const float y = dstrect->y;
const float width = dstrect->w;
const float height = dstrect->h;
const float u0 = srcrect->x;
const float v0 = srcrect->y;
const float u1 = srcrect->x + srcrect->w;
const float v1 = srcrect->y + srcrect->h;
verts = (VertVCT *) SDL_AllocateRenderVertices(renderer, 2 * sizeof (VertVCT), 4, &cmd->data.draw.first);
if (!verts) {
return -1;
}
cmd->data.draw.count = 4;
verts->u = u0;
verts->v = v0;
verts->x = x;
verts->y = y;
verts->col = color;
verts++;
verts->u = u1;
verts->v = v0;
verts->x = x + width;
verts->y = y;
verts->col = color;
verts++;
verts->u = u0;
verts->v = v1;
verts->x = x;
verts->y = y + 1;
verts->col = color;
verts++;
verts->u = u1;
verts->v = v1;
verts->x = x + width;
verts->y = y + height;
verts->col = color;
verts++;
return 0;
}
static int
N3DS_QueueCopyEx(SDL_Renderer * renderer, SDL_RenderCommand *cmd, SDL_Texture * texture,
const SDL_Rect * srcrect, const SDL_FRect * dstrect,
const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip, float scale_x, float scale_y)
{
SDL_Color color = *((SDL_Color*) &(cmd->data.draw.r));
VertVCT *verts = (VertVCT *) SDL_AllocateRenderVertices(renderer, 4 * sizeof (VertVCT), 4, &cmd->data.draw.first);
const float centerx = center->x;
const float centery = center->y;
const float x = dstrect->x + centerx;
const float y = dstrect->y + centery;
const float width = dstrect->w - centerx;
const float height = dstrect->h - centery;
float s, c;
float cw1, sw1, ch1, sh1, cw2, sw2, ch2, sh2;
float u0 = srcrect->x;
float v0 = srcrect->y;
float u1 = srcrect->x + srcrect->w;
float v1 = srcrect->y + srcrect->h;
if (!verts) {
return -1;
}
cmd->data.draw.count = 4;
s = sinf(degToRad(360-angle));
c = cosf(degToRad(360-angle));
cw1 = c * -centerx;
sw1 = s * -centerx;
ch1 = c * -centery;
sh1 = s * -centery;
cw2 = c * width;
sw2 = s * width;
ch2 = c * height;
sh2 = s * height;
if (flip & SDL_FLIP_VERTICAL) {
Swap(&v0, &v1);
}
if (flip & SDL_FLIP_HORIZONTAL) {
Swap(&u0, &u1);
}
verts->u = u0;
verts->v = v0;
verts->x = x + cw1 + sh1;
verts->y = y - sw1 + ch1;
verts->col = color;
verts++;
verts->u = u0;
verts->v = v1;
verts->x = x + cw1 + sh2;
verts->y = y - sw1 + ch2;
verts->col = color;
verts++;
verts->u = u1;
verts->v = v0;
verts->x = x + cw2 + sh1;
verts->y = y - sw2 + ch1;
verts->col = color;
verts++;
verts->u = u1;
verts->v = v1;
verts->x = x + cw2 + sh2;
verts->y = y - sw2 + ch2;
verts->col = color;
if (scale_x != 1.0f || scale_y != 1.0f) {
verts->x *= scale_x;
verts->y *= scale_y;
verts--;
verts->x *= scale_x;
verts->y *= scale_y;
verts--;
verts->x *= scale_x;
verts->y *= scale_y;
verts--;
verts->x *= scale_x;
verts->y *= scale_y;
}
return 0;
}
static void
ResetBlendState(N3DS_RenderData *data, N3DS_BlendState* state) {
state->mode = SDL_BLENDMODE_INVALID;
state->texture = NULL;
C3D_SetTexEnv(0, &data->envNoTex);
}
static void
N3DS_SetBlendState(N3DS_RenderData* data, N3DS_BlendState* state)
{
N3DS_BlendState* current = &data->blendState;
if (state->mode != current->mode) {
switch (state->mode) {
case SDL_BLENDMODE_NONE:
C3D_AlphaBlend(GPU_BLEND_ADD, GPU_BLEND_ADD, GPU_ONE, GPU_ZERO, GPU_ONE, GPU_ZERO);
break;
case SDL_BLENDMODE_BLEND:
C3D_AlphaBlend(GPU_BLEND_ADD, GPU_BLEND_ADD, GPU_SRC_ALPHA, GPU_ONE_MINUS_SRC_ALPHA, GPU_SRC_ALPHA, GPU_ONE_MINUS_SRC_ALPHA);
break;
case SDL_BLENDMODE_ADD:
C3D_AlphaBlend(GPU_BLEND_ADD, GPU_BLEND_ADD, GPU_SRC_ALPHA, GPU_ONE, GPU_ZERO, GPU_ONE);
break;
case SDL_BLENDMODE_MOD:
C3D_AlphaBlend(GPU_BLEND_ADD, GPU_BLEND_ADD, GPU_DST_COLOR, GPU_ZERO, GPU_ZERO, GPU_ONE);
break;
case SDL_BLENDMODE_MUL:
C3D_AlphaBlend(GPU_BLEND_ADD, GPU_BLEND_ADD, GPU_DST_COLOR, GPU_ONE_MINUS_SRC_ALPHA, GPU_DST_ALPHA, GPU_ONE_MINUS_SRC_ALPHA);
break;
case SDL_BLENDMODE_INVALID:
break;
}
}
if(state->texture != current->texture) {
if(state->texture != NULL) {
TextureActivate(state->texture);
C3D_SetTexEnv(0, &data->envTex);
} else {
C3D_SetTexEnv(0,&data->envNoTex);
}
}
*current = *state;
}
static int
N3DS_RunCommandQueue(SDL_Renderer * renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize)
{
N3DS_RenderData *data = (N3DS_RenderData *) renderer->driverdata;
C3D_BufInfo *bufInfo = C3D_GetBufInfo();
BufInfo_Init(bufInfo);
BufInfo_Add(bufInfo, vertices, sizeof(VertVCT), 3, 0x210);
while (cmd) {
switch (cmd->command) {
case SDL_RENDERCMD_SETVIEWPORT: {
SDL_Rect *viewport = &cmd->data.viewport.rect;
C3D_SetViewport(viewport->x, viewport->y, viewport->w, viewport->h);
C3D_SetScissor(GPU_SCISSOR_NORMAL, viewport->x, viewport->y, viewport->x + viewport->w, viewport->y + viewport->h);
break;
}
case SDL_RENDERCMD_SETDRAWCOLOR: {
break;
}
case SDL_RENDERCMD_DRAW_POINTS: {
/* Output as geometry */
break;
}
case SDL_RENDERCMD_DRAW_LINES: {
/* Output as geometry */
break;
}
case SDL_RENDERCMD_SETCLIPRECT: {
const SDL_Rect *rect = &cmd->data.cliprect.rect;
if(cmd->data.cliprect.enabled){
C3D_SetScissor(GPU_SCISSOR_NORMAL, rect->x, rect->y, rect->x + rect->w, rect->y + rect->h);
} else {
C3D_SetScissor(GPU_SCISSOR_DISABLE, 0, 0, 0, 0);
}
break;
}
case SDL_RENDERCMD_CLEAR: {
const Uint8 r = cmd->data.color.r;
const Uint8 g = cmd->data.color.g;
const Uint8 b = cmd->data.color.b;
const Uint8 a = cmd->data.color.a;
C3D_FrameBufClear(
C3D_GetFrameBuf(),
C3D_CLEAR_ALL,
COL8888(r, g, b, a),
0
);
break;
}
case SDL_RENDERCMD_FILL_RECTS: {
const size_t first = cmd->data.draw.first;
const size_t count = cmd->data.draw.count;
N3DS_BlendState state = {
.texture = NULL,
.mode = cmd->data.draw.blend
};
N3DS_SetBlendState(data, &state);
C3D_DrawArrays(GPU_TRIANGLE_STRIP, first, count);
break;
}
case SDL_RENDERCMD_COPY:
case SDL_RENDERCMD_COPY_EX: {
const size_t first = cmd->data.draw.first;
const size_t count = cmd->data.draw.count;
N3DS_BlendState state = {
.texture = cmd->data.draw.texture,
.mode = cmd->data.draw.blend
};
N3DS_SetBlendState(data, &state);
C3D_DrawArrays(GPU_TRIANGLE_STRIP, first, count);
break;
}
case SDL_RENDERCMD_GEOMETRY: {
const size_t first = cmd->data.draw.first;
const size_t count = cmd->data.draw.count;
N3DS_BlendState state = {
.texture = cmd->data.draw.texture,
.mode = cmd->data.draw.blend
};
N3DS_SetBlendState(data, &state);
C3D_DrawArrays(GPU_TRIANGLES, first, count);
break;
}
case SDL_RENDERCMD_NO_OP:
break;
}
cmd = cmd->next;
}
return 0;
}
static int
N3DS_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect,
Uint32 pixel_format, void * pixels, int pitch)
{
return SDL_Unsupported();
}
static int
N3DS_RenderPresent(SDL_Renderer * renderer)
{
N3DS_RenderData *data = (N3DS_RenderData *) renderer->driverdata;
C3D_FrameEnd(0);
C3D_FrameBegin(data->vsync ? C3D_FRAME_SYNCDRAW : 0);
N3DS_SetRenderTarget(renderer, data->boundTarget);
return 0;
}
static void
N3DS_DestroyTexture(SDL_Renderer * renderer, SDL_Texture * texture)
{
N3DS_RenderData *renderdata = (N3DS_RenderData *) renderer->driverdata;
N3DS_TextureData *N3DS_texture = (N3DS_TextureData *) texture->driverdata;
if (renderdata == 0)
return;
if (N3DS_texture == 0)
return;
if (N3DS_texture->renderTarget != NULL) {
C3D_RenderTargetDelete(N3DS_texture->renderTarget);
}
if (N3DS_texture->unswizzledBuffer != NULL) {
linearFree(N3DS_texture->unswizzledBuffer);
}
C3D_TexDelete(&N3DS_texture->texture);
SDL_free(N3DS_texture);
texture->driverdata = NULL;
}
static void
N3DS_DestroyRenderer(SDL_Renderer * renderer)
{
N3DS_RenderData *data = (N3DS_RenderData *) renderer->driverdata;
if (data) {
if (!data->initialized)
return;
C3D_RenderTargetDelete(data->renderTarget);
shaderProgramFree(&data->shaderProgram);
DVLB_Free(data->dvlb);
C3D_Fini();
data->initialized = SDL_FALSE;
SDL_free(data);
}
}
static int
N3DS_SetVSync(SDL_Renderer * renderer, const int vsync)
{
N3DS_RenderData *data = renderer->driverdata;
data->vsync = vsync;
return 0;
}
int
N3DS_CreateRenderer(SDL_Renderer * renderer, SDL_Window * window, Uint32 flags)
{
N3DS_RenderData *data;
int width, height, pixelFormat;
C3D_AttrInfo *attrInfo;
bool windowIsBottom;
data = (N3DS_RenderData *) SDL_calloc(1, sizeof(*data));
if (!data) {
N3DS_DestroyRenderer(renderer);
return SDL_OutOfMemory();
}
renderer->WindowEvent = N3DS_WindowEvent;
renderer->CreateTexture = N3DS_CreateTexture;
renderer->UpdateTexture = N3DS_UpdateTexture;
renderer->LockTexture = N3DS_LockTexture;
renderer->UnlockTexture = N3DS_UnlockTexture;
renderer->SetTextureScaleMode = N3DS_SetTextureScaleMode;
renderer->SetRenderTarget = N3DS_SetRenderTarget;
renderer->QueueSetViewport = N3DS_QueueSetViewport;
renderer->QueueSetDrawColor = N3DS_QueueSetViewport; /* SetViewport and SetDrawColor are (currently) no-ops. */
renderer->QueueGeometry = N3DS_QueueGeometry;
renderer->QueueFillRects = N3DS_QueueFillRects;
renderer->QueueCopy = N3DS_QueueCopy;
renderer->QueueCopyEx = N3DS_QueueCopyEx;
renderer->RunCommandQueue = N3DS_RunCommandQueue;
renderer->RenderReadPixels = N3DS_RenderReadPixels;
renderer->RenderPresent = N3DS_RenderPresent;
renderer->DestroyTexture = N3DS_DestroyTexture;
renderer->DestroyRenderer = N3DS_DestroyRenderer;
renderer->SetVSync = N3DS_SetVSync;
renderer->info = N3DS_RenderDriver.info;
renderer->info.flags = (SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
renderer->driverdata = data;
renderer->window = window;
renderer->point_method = SDL_RENDERPOINTMETHOD_GEOMETRY;
renderer->line_method = SDL_RENDERLINEMETHOD_GEOMETRY;
if (data->initialized != SDL_FALSE)
return 0;
data->initialized = SDL_TRUE;
if (flags & SDL_RENDERER_PRESENTVSYNC) {
data->vsync = SDL_TRUE;
} else {
data->vsync = SDL_FALSE;
}
C3D_Init(C3D_DEFAULT_CMDBUF_SIZE);
/* Load shader */
data->dvlb = DVLB_ParseFile((uint32_t*) n3ds_shader_v, sizeof(n3ds_shader_v));
shaderProgramInit(&data->shaderProgram);
shaderProgramSetVsh(&data->shaderProgram, &data->dvlb->DVLE[0]);
data->projMtxShaderLoc = shaderInstanceGetUniformLocation(data->shaderProgram.vertexShader, "projection");
/* Create render targets */
SDL_GetWindowSizeInPixels(window, &width, &height);
pixelFormat = PixelFormatToN3DSGPU(SDL_GetWindowPixelFormat(window));
/* FIXME: We might need a more resilient way of detecting the window<->screen mapping in the future. */
windowIsBottom = (width == 320);
data->renderTarget = C3D_RenderTargetCreate(height, width, pixelFormat, GPU_RB_DEPTH16);
data->boundTarget = NULL;
C3D_RenderTargetClear(data->renderTarget, C3D_CLEAR_ALL, 0, 0);
C3D_RenderTargetSetOutput(data->renderTarget,
windowIsBottom ? GFX_BOTTOM : GFX_TOP, GFX_LEFT,
GX_TRANSFER_IN_FORMAT(pixelFormat) | GX_TRANSFER_OUT_FORMAT(GPU_RB_RGBA8));
Mtx_OrthoTilt(&data->renderProjMtx, 0.0, width, 0.0, height, -1.0, 1.0, true);
C3D_FrameBegin(data->vsync ? C3D_FRAME_SYNCDRAW : 0);
N3DS_SetRenderTarget(renderer, NULL);
C3D_DepthTest(false, GPU_GEQUAL, GPU_WRITE_ALL);
/* Scissoring */
C3D_SetScissor(GPU_SCISSOR_NORMAL, 0, 0, width, height);
/* Bind shader */
C3D_BindProgram(&data->shaderProgram);
attrInfo = C3D_GetAttrInfo();
AttrInfo_Init(attrInfo);
AttrInfo_AddLoader(attrInfo, 0, GPU_FLOAT, 2);
AttrInfo_AddLoader(attrInfo, 1, GPU_UNSIGNED_BYTE, 4);
AttrInfo_AddLoader(attrInfo, 2, GPU_FLOAT, 2);
C3D_SetAttrInfo(attrInfo);
/* Texture environments */
C3D_TexEnvInit(&data->envTex);
C3D_TexEnvSrc(&data->envTex, C3D_Both, GPU_TEXTURE0, (GPU_TEVSRC) 0, (GPU_TEVSRC) 0);
C3D_TexEnvOpRgb(&data->envTex, (GPU_TEVOP_RGB) 0, (GPU_TEVOP_RGB) 0, (GPU_TEVOP_RGB) 0);
C3D_TexEnvOpAlpha(&data->envTex, (GPU_TEVOP_A) 0, (GPU_TEVOP_A) 0, (GPU_TEVOP_A) 0);
C3D_TexEnvFunc(&data->envTex, C3D_Both, GPU_MODULATE);
C3D_TexEnvInit(&data->envNoTex);
C3D_TexEnvSrc(&data->envNoTex, C3D_Both, GPU_PRIMARY_COLOR, (GPU_TEVSRC) 0, (GPU_TEVSRC) 0);
C3D_TexEnvOpRgb(&data->envNoTex, (GPU_TEVOP_RGB) 0, (GPU_TEVOP_RGB) 0, (GPU_TEVOP_RGB) 0);
C3D_TexEnvOpAlpha(&data->envNoTex, (GPU_TEVOP_A) 0, (GPU_TEVOP_A) 0, (GPU_TEVOP_A) 0);
C3D_TexEnvFunc(&data->envNoTex, C3D_Both, GPU_REPLACE);
ResetBlendState(data, &data->blendState);
return 0;
}
SDL_RenderDriver N3DS_RenderDriver = {
.CreateRenderer = N3DS_CreateRenderer,
.info = {
.name = "N3DS",
.flags = SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_TARGETTEXTURE,
.num_texture_formats = 5,
.texture_formats = {
[0] = SDL_PIXELFORMAT_RGBA8888, // GPU_RGBA8
[1] = SDL_PIXELFORMAT_RGB888, // GPU_RGB8
[2] = SDL_PIXELFORMAT_RGBA5551, // GPU_RGBA5551
[3] = SDL_PIXELFORMAT_RGB565, // GPU_RGB565
[4] = SDL_PIXELFORMAT_RGBA4444 // GPU_RGBA4
},
.max_texture_width = 1024,
.max_texture_height = 1024,
}
};
#endif /* SDL_VIDEO_RENDER_N3DS */
/* vi: set ts=4 sw=4 expandtab: */

View File

@ -0,0 +1,49 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
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.
*/
#ifndef SDL_RENDER_N3DS_SHADERS_H
#define SDL_RENDER_N3DS_SHADERS_H
unsigned char n3ds_shader_v[] = {
0x44, 0x56, 0x4c, 0x42, 0x01, 0x00, 0x00, 0x00, 0xa4, 0x00, 0x00, 0x00, 0x44, 0x56, 0x4c, 0x50,
0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
0x09, 0x00, 0x00, 0x00, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x01, 0xf0, 0x07, 0x4e, 0x02, 0x08, 0x02, 0x08,
0x03, 0x18, 0x02, 0x08, 0x04, 0x28, 0x02, 0x08, 0x05, 0x38, 0x02, 0x08, 0x06, 0x20, 0x20, 0x4c,
0x07, 0xf0, 0x27, 0x4e, 0x88, 0x18, 0x40, 0x20, 0x00, 0x00, 0x00, 0x88, 0x6c, 0x03, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xa3, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0xc3, 0x06, 0x00,
0x00, 0x00, 0x00, 0x00, 0x64, 0xc3, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0xc3, 0x06, 0x00,
0x00, 0x00, 0x00, 0x00, 0x61, 0xc3, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6f, 0x03, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x4f, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8f, 0x1c, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x44, 0x56, 0x4c, 0x45, 0x02, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x00,
0x0b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x5f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00,
0x01, 0x01, 0x37, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
0x03, 0x00, 0x01, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x0f, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x13, 0x00, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x69,
0x6f, 0x6e, 0x00, 0x00,
};
#endif // SDL_RENDER_N3DS_SHADERS_H
/* vi: set ts=4 sw=4 expandtab: */

View File

@ -0,0 +1,50 @@
; Simple DirectMedia Layer
; Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
;
; 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.
.fvec projection[4]
.out outpos position
.out outtc0 texcoord0
.out outclr color
.alias inpos v0
.alias inclr v1
.alias intc0 v2
.constf const(0.0, 1.0, 0.00392156862745098, 1.0)
.alias ZEROS const.xxxx
.alias ONES const.yyyy
.alias RGBS const.zzzz
.proc main
mov r0.xy, inpos
mov r0.zw, ONES
dp4 outpos.x, projection[0], r0
dp4 outpos.y, projection[1], r0
dp4 outpos.z, projection[2], r0
dp4 outpos.w, projection[3], r0
mov outtc0, intc0
mov r1, RGBS
mul outclr, inclr.wzyx, r1.x
end
.end

View File

@ -215,7 +215,11 @@ add_sdl_test_executable(testvulkan testvulkan.c)
add_sdl_test_executable(testoffscreen testoffscreen.c)
if(N3DS)
sdltest_link_librararies(SDL2::SDL2main)
sdltest_link_librararies(
SDL2::SDL2main
SDL2::SDL2-static
citro3d
)
endif()
if(PSP)