Last active
September 21, 2023 02:08
-
-
Save jessechounard/d4252efc12ee24494484611d92b1debe to your computer and use it in GitHub Desktop.
Simple texture rendering in FNA3D
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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