mirror of
https://github.com/switchbrew/switch-examples.git
synced 2025-06-20 21:12:38 +02:00
548 lines
17 KiB
C++
548 lines
17 KiB
C++
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <switch.h>
|
|
|
|
#include <EGL/egl.h> // EGL library
|
|
#include <EGL/eglext.h> // EGL extensions
|
|
#include <glad/glad.h> // glad library (OpenGL loader)
|
|
|
|
// GLM headers
|
|
#define GLM_FORCE_PURE
|
|
#include <glm/vec3.hpp>
|
|
#include <glm/vec4.hpp>
|
|
#include <glm/mat4x4.hpp>
|
|
#include <glm/gtc/type_ptr.hpp>
|
|
#include <glm/gtc/matrix_transform.hpp>
|
|
|
|
#include "stb_image.h"
|
|
#include "devkitlenny_png.h"
|
|
|
|
constexpr auto TAU = glm::two_pi<float>();
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// nxlink support
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#ifndef ENABLE_NXLINK
|
|
#define TRACE(fmt,...) ((void)0)
|
|
#else
|
|
#include <unistd.h>
|
|
#define TRACE(fmt,...) printf("%s: " fmt "\n", __PRETTY_FUNCTION__, ## __VA_ARGS__)
|
|
|
|
static int s_nxlinkSock = -1;
|
|
|
|
static void initNxLink()
|
|
{
|
|
if (R_FAILED(socketInitializeDefault()))
|
|
return;
|
|
|
|
s_nxlinkSock = nxlinkStdio();
|
|
if (s_nxlinkSock >= 0)
|
|
TRACE("printf output now goes to nxlink server");
|
|
else
|
|
socketExit();
|
|
}
|
|
|
|
static void deinitNxLink()
|
|
{
|
|
if (s_nxlinkSock >= 0)
|
|
{
|
|
close(s_nxlinkSock);
|
|
socketExit();
|
|
s_nxlinkSock = -1;
|
|
}
|
|
}
|
|
|
|
extern "C" void userAppInit()
|
|
{
|
|
initNxLink();
|
|
}
|
|
|
|
extern "C" void userAppExit()
|
|
{
|
|
deinitNxLink();
|
|
}
|
|
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// EGL initialization
|
|
//-----------------------------------------------------------------------------
|
|
|
|
static EGLDisplay s_display;
|
|
static EGLContext s_context;
|
|
static EGLSurface s_surface;
|
|
|
|
static bool initEgl(NWindow* win)
|
|
{
|
|
// Connect to the EGL default display
|
|
s_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
|
if (!s_display)
|
|
{
|
|
TRACE("Could not connect to display! error: %d", eglGetError());
|
|
goto _fail0;
|
|
}
|
|
|
|
// Initialize the EGL display connection
|
|
eglInitialize(s_display, nullptr, nullptr);
|
|
|
|
// Select OpenGL (Core) as the desired graphics API
|
|
if (eglBindAPI(EGL_OPENGL_API) == EGL_FALSE)
|
|
{
|
|
TRACE("Could not set API! error: %d", eglGetError());
|
|
goto _fail1;
|
|
}
|
|
|
|
// Get an appropriate EGL framebuffer configuration
|
|
EGLConfig config;
|
|
EGLint numConfigs;
|
|
static const EGLint framebufferAttributeList[] =
|
|
{
|
|
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
|
|
EGL_RED_SIZE, 8,
|
|
EGL_GREEN_SIZE, 8,
|
|
EGL_BLUE_SIZE, 8,
|
|
EGL_ALPHA_SIZE, 8,
|
|
EGL_DEPTH_SIZE, 24,
|
|
EGL_STENCIL_SIZE, 8,
|
|
EGL_NONE
|
|
};
|
|
eglChooseConfig(s_display, framebufferAttributeList, &config, 1, &numConfigs);
|
|
if (numConfigs == 0)
|
|
{
|
|
TRACE("No config found! error: %d", eglGetError());
|
|
goto _fail1;
|
|
}
|
|
|
|
// Create an EGL window surface
|
|
s_surface = eglCreateWindowSurface(s_display, config, win, nullptr);
|
|
if (!s_surface)
|
|
{
|
|
TRACE("Surface creation failed! error: %d", eglGetError());
|
|
goto _fail1;
|
|
}
|
|
|
|
// Create an EGL rendering context
|
|
static const EGLint contextAttributeList[] =
|
|
{
|
|
EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR,
|
|
EGL_CONTEXT_MAJOR_VERSION_KHR, 4,
|
|
EGL_CONTEXT_MINOR_VERSION_KHR, 3,
|
|
EGL_NONE
|
|
};
|
|
s_context = eglCreateContext(s_display, config, EGL_NO_CONTEXT, contextAttributeList);
|
|
if (!s_context)
|
|
{
|
|
TRACE("Context creation failed! error: %d", eglGetError());
|
|
goto _fail2;
|
|
}
|
|
|
|
// Connect the context to the surface
|
|
eglMakeCurrent(s_display, s_surface, s_surface, s_context);
|
|
return true;
|
|
|
|
_fail2:
|
|
eglDestroySurface(s_display, s_surface);
|
|
s_surface = nullptr;
|
|
_fail1:
|
|
eglTerminate(s_display);
|
|
s_display = nullptr;
|
|
_fail0:
|
|
return false;
|
|
}
|
|
|
|
static void deinitEgl()
|
|
{
|
|
if (s_display)
|
|
{
|
|
eglMakeCurrent(s_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
|
if (s_context)
|
|
{
|
|
eglDestroyContext(s_display, s_context);
|
|
s_context = nullptr;
|
|
}
|
|
if (s_surface)
|
|
{
|
|
eglDestroySurface(s_display, s_surface);
|
|
s_surface = nullptr;
|
|
}
|
|
eglTerminate(s_display);
|
|
s_display = nullptr;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Main program
|
|
//-----------------------------------------------------------------------------
|
|
|
|
static void setMesaConfig()
|
|
{
|
|
// Uncomment below to disable error checking and save CPU time (useful for production):
|
|
//setenv("MESA_NO_ERROR", "1", 1);
|
|
|
|
// Uncomment below to enable Mesa logging:
|
|
//setenv("EGL_LOG_LEVEL", "debug", 1);
|
|
//setenv("MESA_VERBOSE", "all", 1);
|
|
//setenv("NOUVEAU_MESA_DEBUG", "1", 1);
|
|
|
|
// Uncomment below to enable shader debugging in Nouveau:
|
|
//setenv("NV50_PROG_OPTIMIZE", "0", 1);
|
|
//setenv("NV50_PROG_DEBUG", "1", 1);
|
|
//setenv("NV50_PROG_CHIPSET", "0x120", 1);
|
|
}
|
|
|
|
static const char* const vertexShaderSource = R"text(
|
|
#version 320 es
|
|
precision mediump float;
|
|
|
|
layout (location = 0) in vec3 inPos;
|
|
layout (location = 1) in vec2 inTexCoord;
|
|
layout (location = 2) in vec3 inNormal;
|
|
|
|
out vec2 vtxTexCoord;
|
|
out vec4 vtxNormalQuat;
|
|
out vec3 vtxView;
|
|
|
|
uniform mat4 mdlvMtx;
|
|
uniform mat4 projMtx;
|
|
|
|
void main()
|
|
{
|
|
// Calculate position
|
|
vec4 pos = mdlvMtx * vec4(inPos, 1.0);
|
|
vtxView = -pos.xyz;
|
|
gl_Position = projMtx * pos;
|
|
|
|
// Calculate normalquat
|
|
vec3 normal = normalize(mat3(mdlvMtx) * inNormal);
|
|
float z = (1.0 + normal.z) / 2.0;
|
|
vtxNormalQuat = vec4(1.0, 0.0, 0.0, 0.0);
|
|
if (z > 0.0)
|
|
{
|
|
vtxNormalQuat.z = sqrt(z);
|
|
vtxNormalQuat.xy = normal.xy / (2.0 * vtxNormalQuat.z);
|
|
}
|
|
|
|
// Calculate texcoord
|
|
vtxTexCoord = inTexCoord;
|
|
}
|
|
)text";
|
|
|
|
static const char* const fragmentShaderSource = R"text(
|
|
#version 320 es
|
|
precision mediump float;
|
|
|
|
in vec2 vtxTexCoord;
|
|
in vec4 vtxNormalQuat;
|
|
in vec3 vtxView;
|
|
|
|
out vec4 fragColor;
|
|
|
|
uniform vec4 lightPos;
|
|
uniform vec3 ambient;
|
|
uniform vec3 diffuse;
|
|
uniform vec4 specular; // w component is shininess
|
|
|
|
uniform sampler2D tex_diffuse;
|
|
|
|
// Rotate the vector v by the quaternion q
|
|
vec3 quatrotate(vec4 q, vec3 v)
|
|
{
|
|
return v + 2.0*cross(q.xyz, cross(q.xyz, v) + q.w*v);
|
|
}
|
|
|
|
void main()
|
|
{
|
|
// Extract normal from quaternion
|
|
vec4 normquat = normalize(vtxNormalQuat);
|
|
vec3 normal = quatrotate(normquat, vec3(0.0, 0.0, 1.0));
|
|
|
|
vec3 lightVec;
|
|
if (lightPos.w != 0.0)
|
|
lightVec = normalize(lightPos.xyz + vtxView);
|
|
else
|
|
lightVec = normalize(lightPos.xyz);
|
|
|
|
vec3 viewVec = normalize(vtxView);
|
|
vec3 halfVec = normalize(viewVec + lightVec);
|
|
float diffuseFactor = max(dot(lightVec, normal), 0.0);
|
|
float specularFactor = pow(max(dot(normal, halfVec), 0.0), specular.w);
|
|
|
|
vec4 texDiffuseColor = texture(tex_diffuse, vtxTexCoord);
|
|
vec3 fragLightColor = ambient + diffuseFactor*diffuse*texDiffuseColor.rgb + specularFactor*specular.xyz;
|
|
|
|
fragColor = vec4(min(fragLightColor, 1.0), texDiffuseColor.a);
|
|
}
|
|
)text";
|
|
|
|
static GLuint createAndCompileShader(GLenum type, const char* source)
|
|
{
|
|
GLint success;
|
|
GLchar msg[512];
|
|
|
|
GLuint handle = glCreateShader(type);
|
|
if (!handle)
|
|
{
|
|
TRACE("%u: cannot create shader", type);
|
|
return 0;
|
|
}
|
|
glShaderSource(handle, 1, &source, nullptr);
|
|
glCompileShader(handle);
|
|
glGetShaderiv(handle, GL_COMPILE_STATUS, &success);
|
|
|
|
if (success == GL_FALSE)
|
|
{
|
|
glGetShaderInfoLog(handle, sizeof(msg), nullptr, msg);
|
|
TRACE("%u: %s\n", type, msg);
|
|
glDeleteShader(handle);
|
|
return 0;
|
|
}
|
|
|
|
return handle;
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
float position[3];
|
|
float texcoord[2];
|
|
float normal[3];
|
|
} Vertex;
|
|
|
|
static const Vertex vertex_list[] =
|
|
{
|
|
// First face (PZ)
|
|
// First triangle
|
|
{ {-0.5f, -0.5f, +0.5f}, {0.0f, 0.0f}, {0.0f, 0.0f, +1.0f} },
|
|
{ {+0.5f, -0.5f, +0.5f}, {1.0f, 0.0f}, {0.0f, 0.0f, +1.0f} },
|
|
{ {+0.5f, +0.5f, +0.5f}, {1.0f, 1.0f}, {0.0f, 0.0f, +1.0f} },
|
|
// Second triangle
|
|
{ {+0.5f, +0.5f, +0.5f}, {1.0f, 1.0f}, {0.0f, 0.0f, +1.0f} },
|
|
{ {-0.5f, +0.5f, +0.5f}, {0.0f, 1.0f}, {0.0f, 0.0f, +1.0f} },
|
|
{ {-0.5f, -0.5f, +0.5f}, {0.0f, 0.0f}, {0.0f, 0.0f, +1.0f} },
|
|
|
|
// Second face (MZ)
|
|
// First triangle
|
|
{ {-0.5f, -0.5f, -0.5f}, {0.0f, 0.0f}, {0.0f, 0.0f, -1.0f} },
|
|
{ {-0.5f, +0.5f, -0.5f}, {1.0f, 0.0f}, {0.0f, 0.0f, -1.0f} },
|
|
{ {+0.5f, +0.5f, -0.5f}, {1.0f, 1.0f}, {0.0f, 0.0f, -1.0f} },
|
|
// Second triangle
|
|
{ {+0.5f, +0.5f, -0.5f}, {1.0f, 1.0f}, {0.0f, 0.0f, -1.0f} },
|
|
{ {+0.5f, -0.5f, -0.5f}, {0.0f, 1.0f}, {0.0f, 0.0f, -1.0f} },
|
|
{ {-0.5f, -0.5f, -0.5f}, {0.0f, 0.0f}, {0.0f, 0.0f, -1.0f} },
|
|
|
|
// Third face (PX)
|
|
// First triangle
|
|
{ {+0.5f, -0.5f, -0.5f}, {0.0f, 0.0f}, {+1.0f, 0.0f, 0.0f} },
|
|
{ {+0.5f, +0.5f, -0.5f}, {1.0f, 0.0f}, {+1.0f, 0.0f, 0.0f} },
|
|
{ {+0.5f, +0.5f, +0.5f}, {1.0f, 1.0f}, {+1.0f, 0.0f, 0.0f} },
|
|
// Second triangle
|
|
{ {+0.5f, +0.5f, +0.5f}, {1.0f, 1.0f}, {+1.0f, 0.0f, 0.0f} },
|
|
{ {+0.5f, -0.5f, +0.5f}, {0.0f, 1.0f}, {+1.0f, 0.0f, 0.0f} },
|
|
{ {+0.5f, -0.5f, -0.5f}, {0.0f, 0.0f}, {+1.0f, 0.0f, 0.0f} },
|
|
|
|
// Fourth face (MX)
|
|
// First triangle
|
|
{ {-0.5f, -0.5f, -0.5f}, {0.0f, 0.0f}, {-1.0f, 0.0f, 0.0f} },
|
|
{ {-0.5f, -0.5f, +0.5f}, {1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f} },
|
|
{ {-0.5f, +0.5f, +0.5f}, {1.0f, 1.0f}, {-1.0f, 0.0f, 0.0f} },
|
|
// Second triangle
|
|
{ {-0.5f, +0.5f, +0.5f}, {1.0f, 1.0f}, {-1.0f, 0.0f, 0.0f} },
|
|
{ {-0.5f, +0.5f, -0.5f}, {0.0f, 1.0f}, {-1.0f, 0.0f, 0.0f} },
|
|
{ {-0.5f, -0.5f, -0.5f}, {0.0f, 0.0f}, {-1.0f, 0.0f, 0.0f} },
|
|
|
|
// Fifth face (PY)
|
|
// First triangle
|
|
{ {-0.5f, +0.5f, -0.5f}, {0.0f, 0.0f}, {0.0f, +1.0f, 0.0f} },
|
|
{ {-0.5f, +0.5f, +0.5f}, {1.0f, 0.0f}, {0.0f, +1.0f, 0.0f} },
|
|
{ {+0.5f, +0.5f, +0.5f}, {1.0f, 1.0f}, {0.0f, +1.0f, 0.0f} },
|
|
// Second triangle
|
|
{ {+0.5f, +0.5f, +0.5f}, {1.0f, 1.0f}, {0.0f, +1.0f, 0.0f} },
|
|
{ {+0.5f, +0.5f, -0.5f}, {0.0f, 1.0f}, {0.0f, +1.0f, 0.0f} },
|
|
{ {-0.5f, +0.5f, -0.5f}, {0.0f, 0.0f}, {0.0f, +1.0f, 0.0f} },
|
|
|
|
// Sixth face (MY)
|
|
// First triangle
|
|
{ {-0.5f, -0.5f, -0.5f}, {0.0f, 0.0f}, {0.0f, -1.0f, 0.0f} },
|
|
{ {+0.5f, -0.5f, -0.5f}, {1.0f, 0.0f}, {0.0f, -1.0f, 0.0f} },
|
|
{ {+0.5f, -0.5f, +0.5f}, {1.0f, 1.0f}, {0.0f, -1.0f, 0.0f} },
|
|
// Second triangle
|
|
{ {+0.5f, -0.5f, +0.5f}, {1.0f, 1.0f}, {0.0f, -1.0f, 0.0f} },
|
|
{ {-0.5f, -0.5f, +0.5f}, {0.0f, 1.0f}, {0.0f, -1.0f, 0.0f} },
|
|
{ {-0.5f, -0.5f, -0.5f}, {0.0f, 0.0f}, {0.0f, -1.0f, 0.0f} },
|
|
};
|
|
|
|
#define vertex_list_count (sizeof(vertex_list)/sizeof(vertex_list[0]))
|
|
|
|
static GLuint s_program;
|
|
static GLuint s_vao, s_vbo;
|
|
static GLuint s_tex;
|
|
|
|
static GLint loc_mdlvMtx, loc_projMtx;
|
|
static GLint loc_lightPos, loc_ambient, loc_diffuse, loc_specular, loc_tex_diffuse;
|
|
|
|
static u64 s_startTicks;
|
|
|
|
static void sceneInit()
|
|
{
|
|
GLint vsh = createAndCompileShader(GL_VERTEX_SHADER, vertexShaderSource);
|
|
GLint fsh = createAndCompileShader(GL_FRAGMENT_SHADER, fragmentShaderSource);
|
|
|
|
s_program = glCreateProgram();
|
|
glAttachShader(s_program, vsh);
|
|
glAttachShader(s_program, fsh);
|
|
glLinkProgram(s_program);
|
|
|
|
GLint success;
|
|
glGetProgramiv(s_program, GL_LINK_STATUS, &success);
|
|
if (success == GL_FALSE)
|
|
{
|
|
char buf[512];
|
|
glGetProgramInfoLog(s_program, sizeof(buf), nullptr, buf);
|
|
TRACE("Link error: %s", buf);
|
|
}
|
|
glDeleteShader(vsh);
|
|
glDeleteShader(fsh);
|
|
|
|
loc_mdlvMtx = glGetUniformLocation(s_program, "mdlvMtx");
|
|
loc_projMtx = glGetUniformLocation(s_program, "projMtx");
|
|
loc_lightPos = glGetUniformLocation(s_program, "lightPos");
|
|
loc_ambient = glGetUniformLocation(s_program, "ambient");
|
|
loc_diffuse = glGetUniformLocation(s_program, "diffuse");
|
|
loc_specular = glGetUniformLocation(s_program, "specular");
|
|
loc_tex_diffuse = glGetUniformLocation(s_program, "tex_diffuse");
|
|
|
|
glEnable(GL_DEPTH_TEST);
|
|
glDepthFunc(GL_LESS);
|
|
|
|
glGenVertexArrays(1, &s_vao);
|
|
glGenBuffers(1, &s_vbo);
|
|
// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
|
|
glBindVertexArray(s_vao);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, s_vbo);
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_list), vertex_list, GL_STATIC_DRAW);
|
|
|
|
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, position));
|
|
glEnableVertexAttribArray(0);
|
|
|
|
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, texcoord));
|
|
glEnableVertexAttribArray(1);
|
|
|
|
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, normal));
|
|
glEnableVertexAttribArray(2);
|
|
|
|
// note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
|
// You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other
|
|
// VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary.
|
|
glBindVertexArray(0);
|
|
|
|
// Textures
|
|
glGenTextures(1, &s_tex);
|
|
glActiveTexture(GL_TEXTURE0); // activate the texture unit first before binding texture
|
|
glBindTexture(GL_TEXTURE_2D, s_tex);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
|
|
int width, height, nchan;
|
|
stbi_set_flip_vertically_on_load(true);
|
|
stbi_uc* img = stbi_load_from_memory((const stbi_uc*)devkitlenny_png, devkitlenny_png_size, &width, &height, &nchan, 4);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img);
|
|
stbi_image_free(img);
|
|
|
|
// Uniforms
|
|
glUseProgram(s_program);
|
|
auto projMtx = glm::perspective(40.0f*TAU/360.0f, 1280.0f/720.0f, 0.01f, 1000.0f);
|
|
glUniformMatrix4fv(loc_projMtx, 1, GL_FALSE, glm::value_ptr(projMtx));
|
|
glUniform4f(loc_lightPos, 0.0f, 0.0f, 0.5f, 1.0f);
|
|
glUniform3f(loc_ambient, 0.1f, 0.1f, 0.1f);
|
|
glUniform3f(loc_diffuse, 0.4f, 0.4f, 0.4f);
|
|
glUniform4f(loc_specular, 0.5f, 0.5f, 0.5f, 20.0f);
|
|
glUniform1i(loc_tex_diffuse, 0); // texunit 0
|
|
s_startTicks = armGetSystemTick();
|
|
}
|
|
|
|
static float getTime()
|
|
{
|
|
u64 elapsed = armGetSystemTick() - s_startTicks;
|
|
return (elapsed * 625 / 12) / 1000000000.0;
|
|
}
|
|
|
|
static void sceneUpdate()
|
|
{
|
|
glm::mat4 mdlvMtx{1.0};
|
|
mdlvMtx = glm::translate(mdlvMtx, glm::vec3{0.0f, 0.0f, -3.0f});
|
|
mdlvMtx = glm::rotate(mdlvMtx, getTime() * TAU * 0.234375f, glm::vec3{1.0f, 0.0f, 0.0f});
|
|
mdlvMtx = glm::rotate(mdlvMtx, getTime() * TAU * 0.234375f / 2.0f, glm::vec3{0.0f, 1.0f, 0.0f});
|
|
glUniformMatrix4fv(loc_mdlvMtx, 1, GL_FALSE, glm::value_ptr(mdlvMtx));
|
|
}
|
|
|
|
static void sceneRender()
|
|
{
|
|
glClearColor(0x68/255.0f, 0xB0/255.0f, 0xD8/255.0f, 1.0f);
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
|
|
// draw our textured cube
|
|
glBindVertexArray(s_vao); // seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized
|
|
glDrawArrays(GL_TRIANGLES, 0, vertex_list_count);
|
|
}
|
|
|
|
static void sceneExit()
|
|
{
|
|
glDeleteTextures(1, &s_tex);
|
|
glDeleteBuffers(1, &s_vbo);
|
|
glDeleteVertexArrays(1, &s_vao);
|
|
glDeleteProgram(s_program);
|
|
}
|
|
|
|
int main(int argc, char* argv[])
|
|
{
|
|
// Set mesa configuration (useful for debugging)
|
|
setMesaConfig();
|
|
|
|
// Initialize EGL on the default window
|
|
if (!initEgl(nwindowGetDefault()))
|
|
return EXIT_FAILURE;
|
|
|
|
// Load OpenGL routines using glad
|
|
gladLoadGL();
|
|
|
|
// Initialize our scene
|
|
sceneInit();
|
|
|
|
// Configure our supported input layout: a single player with standard controller styles
|
|
padConfigureInput(1, HidNpadStyleSet_NpadStandard);
|
|
|
|
// Initialize the default gamepad (which reads handheld mode inputs as well as the first connected controller)
|
|
PadState pad;
|
|
padInitializeDefault(&pad);
|
|
|
|
// Main graphics loop
|
|
while (appletMainLoop())
|
|
{
|
|
// Get and process input
|
|
padUpdate(&pad);
|
|
u32 kDown = padGetButtonsDown(&pad);
|
|
if (kDown & HidNpadButton_Plus)
|
|
break;
|
|
|
|
// Update our scene
|
|
sceneUpdate();
|
|
|
|
// Render stuff!
|
|
sceneRender();
|
|
eglSwapBuffers(s_display, s_surface);
|
|
}
|
|
|
|
// Deinitialize our scene
|
|
sceneExit();
|
|
|
|
// Deinitialize EGL
|
|
deinitEgl();
|
|
return EXIT_SUCCESS;
|
|
}
|