Skip to content

Instantly share code, notes, and snippets.

@jessechounard
Last active September 21, 2023 02:08
Show Gist options
  • Select an option

  • Save jessechounard/d4252efc12ee24494484611d92b1debe to your computer and use it in GitHub Desktop.

Select an option

Save jessechounard/d4252efc12ee24494484611d92b1debe to your computer and use it in GitHub Desktop.
Simple texture rendering in FNA3D
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#define MOJOSHADER_NO_VERSION_INCLUDE
#define MOJOSHADER_EFFECT_SUPPORT
#include <mojoshader.h>
#include <mojoshader_effects.h>
#define SDL_MAIN_HANDLED
#include <SDL2/SDL.h>
#include <FNA3D.h>
#include <stb_image.h>
typedef struct Vertex
{
float x, y;
float u, v;
uint32_t color;
} Vertex;
int main(int argc, char *argv[])
{
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) < 0)
{
fprintf(stderr, "Failed to initialize SDL\n\t%s\n", SDL_GetError());
return -1;
}
const int WindowWidth = 1280;
const int WindowHeight = 720;
uint32_t windowFlags = FNA3D_PrepareWindowAttributes();
SDL_Window *window = SDL_CreateWindow("FNA3D Test", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, WindowWidth, WindowHeight, windowFlags);
int width, height;
FNA3D_GetDrawableSize(window, &width, &height);
FNA3D_PresentationParameters presentationParameters;
memset(&presentationParameters, 0, sizeof(presentationParameters));
presentationParameters.backBufferWidth = width;
presentationParameters.backBufferHeight = height;
presentationParameters.deviceWindowHandle = window;
FNA3D_Device *device = FNA3D_CreateDevice(&presentationParameters, 0);
bool quit = false;
double t = 0.0;
double dt = 0.01;
uint64_t currentTime = SDL_GetPerformanceCounter();
double accumulator = 0.0;
// states
FNA3D_Viewport viewport;
memset(&viewport, 0, sizeof(viewport));
viewport.w = width;
viewport.h = height;
FNA3D_SetViewport(device, &viewport);
FNA3D_BlendState blendState;
blendState.alphaBlendFunction = FNA3D_BLENDFUNCTION_ADD;
blendState.alphaDestinationBlend = FNA3D_BLEND_INVERSESOURCEALPHA;
blendState.alphaSourceBlend = FNA3D_BLEND_ONE;
FNA3D_Color blendFactor = { 0xff, 0xff, 0xff, 0xff };
blendState.blendFactor = blendFactor;
blendState.colorBlendFunction = FNA3D_BLENDFUNCTION_ADD;
blendState.colorDestinationBlend = FNA3D_BLEND_INVERSESOURCEALPHA;
blendState.colorSourceBlend = FNA3D_BLEND_ONE;
blendState.colorWriteEnable = FNA3D_COLORWRITECHANNELS_ALL;
blendState.colorWriteEnable1 = FNA3D_COLORWRITECHANNELS_ALL;
blendState.colorWriteEnable2 = FNA3D_COLORWRITECHANNELS_ALL;
blendState.colorWriteEnable3 = FNA3D_COLORWRITECHANNELS_ALL;
blendState.multiSampleMask = -1;
FNA3D_SetBlendState(device, &blendState);
FNA3D_DepthStencilState depthStencilState;
memset(&depthStencilState, 0, sizeof(depthStencilState));
depthStencilState.depthBufferEnable = false;
depthStencilState.stencilEnable = false;
FNA3D_SetDepthStencilState(device, &depthStencilState);
FNA3D_RasterizerState rasterizerState;
rasterizerState.cullMode = FNA3D_CULLMODE_NONE;
rasterizerState.fillMode = FNA3D_FILLMODE_SOLID;
rasterizerState.depthBias = 0;
rasterizerState.multiSampleAntiAlias = 1;
rasterizerState.scissorTestEnable = 0;
rasterizerState.slopeScaleDepthBias = 0;
FNA3D_ApplyRasterizerState(device, &rasterizerState);
// load effect
FNA3D_Effect *effect = NULL;
MOJOSHADER_effect *effectData = NULL;
FILE *effectFile = fopen("SpriteEffect.fxb", "rb");
fseek(effectFile, 0, SEEK_END);
uint32_t effectCodeLength = ftell(effectFile);
fseek(effectFile, 0, SEEK_SET);
uint8_t *effectCode = malloc(effectCodeLength);
fread(effectCode, 1, effectCodeLength, effectFile);
fclose(effectFile);
FNA3D_CreateEffect(device, effectCode, effectCodeLength, &effect, &effectData);
free(effectCode);
// load texture
int imageWidth, imageHeight, imageChannels;
uint8_t *imagePixels = stbi_load("image.png", &imageWidth, &imageHeight, &imageChannels, 4);
FNA3D_Texture *texture = FNA3D_CreateTexture2D(device, FNA3D_SURFACEFORMAT_COLOR, imageWidth, imageHeight, 1, 0);
FNA3D_SetTextureData2D(device, texture, FNA3D_SURFACEFORMAT_COLOR, 0, 0, imageWidth, imageHeight, 0, imagePixels, imageWidth * imageHeight * imageChannels);
stbi_image_free(imagePixels);
FNA3D_VertexElement vertexElements[3];
vertexElements[0].offset = 0;
vertexElements[0].usageIndex = 0;
vertexElements[0].vertexElementFormat = FNA3D_VERTEXELEMENTFORMAT_VECTOR2;
vertexElements[0].vertexElementUsage = FNA3D_VERTEXELEMENTUSAGE_POSITION;
vertexElements[1].offset = sizeof(float) * 2;
vertexElements[1].usageIndex = 0;
vertexElements[1].vertexElementFormat = FNA3D_VERTEXELEMENTFORMAT_VECTOR2;
vertexElements[1].vertexElementUsage = FNA3D_VERTEXELEMENTUSAGE_TEXTURECOORDINATE;
vertexElements[2].offset = sizeof(float) * 4;
vertexElements[2].usageIndex = 0;
vertexElements[2].vertexElementFormat = FNA3D_VERTEXELEMENTFORMAT_COLOR;
vertexElements[2].vertexElementUsage = FNA3D_VERTEXELEMENTUSAGE_COLOR;
FNA3D_VertexDeclaration vertexDeclaration;
vertexDeclaration.elementCount = 3;
vertexDeclaration.vertexStride = sizeof(Vertex);
vertexDeclaration.elements = vertexElements;
Vertex vertices[6] =
{
{ 50, 50, 0, 0, 0xffff0000 },
{ 150, 50, 1, 0, 0xff0000ff },
{ 150, 150, 1, 1, 0xff00ffff },
{ 150, 150, 1, 1, 0xff00ffff },
{ 50, 150, 0, 1, 0xff00ff00 },
{ 50, 50, 0, 0, 0xffff0000 },
};
// vertex buffer
FNA3D_Buffer *vertexBuffer = FNA3D_GenVertexBuffer(device, 0, FNA3D_BUFFERUSAGE_WRITEONLY, 6, sizeof(Vertex));
FNA3D_SetVertexBufferData(device, vertexBuffer, 0, vertices, sizeof(Vertex) * 6, 1, 1, FNA3D_SETDATAOPTIONS_NONE);
FNA3D_VertexBufferBinding vertexBufferBinding;
vertexBufferBinding.instanceFrequency = 0;
vertexBufferBinding.vertexBuffer = vertexBuffer;
vertexBufferBinding.vertexDeclaration = vertexDeclaration;
vertexBufferBinding.vertexOffset = 0;
// index buffer?
while (!quit)
{
SDL_Event event;
while (SDL_PollEvent(&event))
{
switch (event.type)
{
case SDL_QUIT:
quit = true;
break;
}
}
uint64_t newTime = SDL_GetPerformanceCounter();
double frameTime = (newTime - currentTime) / (double)SDL_GetPerformanceFrequency();
if (frameTime > 0.25)
frameTime = 0.25;
currentTime = newTime;
accumulator += frameTime;
bool updateThisLoop = (accumulator >= dt);
while (accumulator >= dt && !quit)
{
// Update here!
t += dt;
accumulator -= dt;
}
if (updateThisLoop && !quit)
{
// Draw here!
FNA3D_BeginFrame(device);
FNA3D_Vec4 color = { 1, 1, 1, 1 };
FNA3D_Clear(device, FNA3D_CLEAROPTIONS_TARGET, &color, 0, 0);
MOJOSHADER_effectStateChanges stateChanges;
memset(&stateChanges, 0, sizeof(stateChanges));
FNA3D_ApplyEffect(device, effect, 0, &stateChanges);
FNA3D_SamplerState samplerState;
memset(&samplerState, 0, sizeof(samplerState));
samplerState.addressU = FNA3D_TEXTUREADDRESSMODE_CLAMP;
samplerState.addressV = FNA3D_TEXTUREADDRESSMODE_CLAMP;
samplerState.addressW = FNA3D_TEXTUREADDRESSMODE_WRAP;
samplerState.filter = FNA3D_TEXTUREFILTER_LINEAR;
samplerState.maxAnisotropy = 4;
samplerState.maxMipLevel = 0;
samplerState.mipMapLevelOfDetailBias = 0;
FNA3D_VerifySampler(device, 0, texture, &samplerState);
for (int i = 0; i < effectData->param_count; i++)
{
if (strcmp("MatrixTransform", effectData->params[i].value.name) == 0)
{
// OrthographicOffCenter Matrix - value copied from XNA project
// todo: Do I need to worry about row-major/column-major?
float projectionMatrix[16] =
{
0.0015625f,
0,
0,
-1,
0,
-0.00277777785f,
0,
1,
0,
0,
1,
0,
0,
0,
0,
1
};
memcpy(effectData->params[i].value.values, projectionMatrix, sizeof(float) * 16);
break;
}
}
// draw primitives
FNA3D_ApplyVertexBufferBindings(device, &vertexBufferBinding, 1, 0, 0);
FNA3D_DrawPrimitives(device, FNA3D_PRIMITIVETYPE_TRIANGLELIST, 0, 2);
FNA3D_SwapBuffers(device, NULL, NULL, window);
}
}
// todo: free vertex buffers (and everything)
FNA3D_DestroyDevice(device);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment