cave-story-solaris/src/Backends/Rendering/Software.cpp
John Lorentzson 2928b8a362 Remove "register" declaration from little endian path
Declaring a "register variable" isn't allowed in modern C++ so I ditch
that in the path that my dev machine compiles.

I *should* remove them from both and update GCC on the Sun to as new
as possible, but I'm not going to do that because that sound painful,
so this is what it is.
2025-04-17 11:34:07 +02:00

407 lines
11 KiB
C++

// Released under the MIT licence.
// See LICENCE.txt for details.
#include "../Rendering.h"
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include "../Misc.h"
#include "Window/Software.h"
#include "../../Attributes.h"
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
typedef struct RenderBackend_Surface
{
unsigned char *pixels;
size_t width;
size_t height;
size_t pitch;
} RenderBackend_Surface;
typedef struct RenderBackend_GlyphAtlas
{
unsigned char *pixels;
size_t width;
size_t height;
} RenderBackend_GlyphAtlas;
static RenderBackend_Surface framebuffer;
static RenderBackend_GlyphAtlas *glyph_atlas;
static RenderBackend_Surface *glyph_destination_surface;
static unsigned char glyph_colour_channels[3];
RenderBackend_Surface* RenderBackend_Init(const char *window_title, size_t screen_width, size_t screen_height, bool fullscreen)
{
if (WindowBackend_Software_CreateWindow(window_title, screen_width, screen_height, fullscreen))
{
framebuffer.pixels = WindowBackend_Software_GetFramebuffer(&framebuffer.pitch);
framebuffer.width = screen_width;
framebuffer.height = screen_height;
return &framebuffer;
}
else
{
Backend_PrintError("Failed to create window");
}
return NULL;
}
void RenderBackend_Deinit(void)
{
WindowBackend_Software_DestroyWindow();
}
void RenderBackend_DrawScreen(void)
{
WindowBackend_Software_Display();
// Backends may use double-buffering, so fetch a new framebuffer just in case
framebuffer.pixels = WindowBackend_Software_GetFramebuffer(&framebuffer.pitch);
}
RenderBackend_Surface* RenderBackend_CreateSurface(size_t width, size_t height, bool render_target)
{
(void)render_target;
RenderBackend_Surface *surface = (RenderBackend_Surface*)malloc(sizeof(RenderBackend_Surface));
if (surface == NULL)
return NULL;
surface->pixels = (unsigned char*)malloc(width * height * 4);
if (surface->pixels == NULL)
{
free(surface);
return NULL;
}
surface->width = width;
surface->height = height;
surface->pitch = width * 4;
return surface;
}
void RenderBackend_FreeSurface(RenderBackend_Surface *surface)
{
free(surface->pixels);
free(surface);
}
bool RenderBackend_IsSurfaceLost(RenderBackend_Surface *surface)
{
(void)surface;
return false;
}
void RenderBackend_RestoreSurface(RenderBackend_Surface *surface)
{
(void)surface;
}
void RenderBackend_UploadSurface(RenderBackend_Surface *surface, const unsigned char *pixels, size_t width, size_t height)
{
for (size_t y = 0; y < height; ++y) {
int srcl = y * width * 3;
int dstl = y * surface->pitch;
for (size_t x = 0; x < width; ++x) {
int srcr = x * 3;
int dstr = x * 4;
#ifdef LITTLE_ENDIAN
surface->pixels[dstl + dstr + 0] = pixels[srcl + srcr + 2];
surface->pixels[dstl + dstr + 1] = pixels[srcl + srcr + 1];
surface->pixels[dstl + dstr + 2] = pixels[srcl + srcr + 0];
#else
surface->pixels[dstl + dstr + 1] = pixels[srcl + srcr + 2];
surface->pixels[dstl + dstr + 2] = pixels[srcl + srcr + 1];
surface->pixels[dstl + dstr + 3] = pixels[srcl + srcr + 0];
#endif
}
}
}
ATTRIBUTE_HOT void RenderBackend_Blit(RenderBackend_Surface *source_surface, const RenderBackend_Rect *rect, RenderBackend_Surface *destination_surface, long x, long y, bool colour_key)
{
RenderBackend_Rect rect_clamped;
rect_clamped.left = rect->left;
rect_clamped.top = rect->top;
rect_clamped.right = rect->right;
rect_clamped.bottom = rect->bottom;
// Clamp the rect and coordinates so we don't write outside the pixel buffer
long overflow;
overflow = 0 - x;
if (overflow > 0)
{
rect_clamped.left += overflow;
x += overflow;
}
overflow = 0 - y;
if (overflow > 0)
{
rect_clamped.top += overflow;
y += overflow;
}
overflow = (x + (rect_clamped.right - rect_clamped.left)) - destination_surface->width;
if (overflow > 0)
rect_clamped.right -= overflow;
overflow = (y + (rect_clamped.bottom - rect_clamped.top)) - destination_surface->height;
if (overflow > 0)
rect_clamped.bottom -= overflow;
if (rect_clamped.bottom - rect_clamped.top <= 0)
return;
if (rect_clamped.right - rect_clamped.left <= 0)
return;
// Do the actual blitting
if (colour_key)
{
for (long j = 0; j < rect_clamped.bottom - rect_clamped.top; ++j)
{
const unsigned char *source_pointer = &source_surface->pixels[((rect_clamped.top + j) * source_surface->pitch) + (rect_clamped.left * 4)];
unsigned char *destination_pointer = &destination_surface->pixels[((y + j) * destination_surface->pitch) + (x * 4)];
for (long i = 0; i < rect_clamped.right - rect_clamped.left; ++i)
{
#ifdef LITTLE_ENDIAN
char red = source_pointer[0];
char green = source_pointer[1];
char blue = source_pointer[2];
#else
register char red = source_pointer[1];
register char green = source_pointer[2];
register char blue = source_pointer[3];
#endif
if (UNLIKELY(red == 0 && green == 0 && blue == 0)) // Assumes the colour key will always be #000000 (black)
{
source_pointer += 4;
destination_pointer += 4;
}
else
{
#ifdef LITTLE_ENDIAN
*destination_pointer++ = *source_pointer++;
*destination_pointer++ = *source_pointer++;
*destination_pointer++ = *source_pointer++;
destination_pointer++;
source_pointer++;
#else
destination_pointer++;
source_pointer++;
*destination_pointer++ = *source_pointer++;
*destination_pointer++ = *source_pointer++;
*destination_pointer++ = *source_pointer++;
#endif
}
}
}
}
else
{
const unsigned char *source_pointer = &source_surface->pixels[(rect_clamped.top * source_surface->pitch) + (rect_clamped.left * 4)];
unsigned char *destination_pointer = &destination_surface->pixels[(y * destination_surface->pitch) + (x * 4)];
for (long j = 0; j < rect_clamped.bottom - rect_clamped.top; ++j)
{
memcpy(destination_pointer, source_pointer, (rect_clamped.right - rect_clamped.left) * 4);
source_pointer += source_surface->pitch;
destination_pointer += destination_surface->pitch;
}
}
}
ATTRIBUTE_HOT void RenderBackend_ColourFill(RenderBackend_Surface *surface, const RenderBackend_Rect *rect, unsigned char red, unsigned char green, unsigned char blue)
{
RenderBackend_Rect rect_clamped;
rect_clamped.left = rect->left;
rect_clamped.top = rect->top;
rect_clamped.right = rect->right;
rect_clamped.bottom = rect->bottom;
// Clamp the rect so it doesn't write outside the pixel buffer
long overflow;
overflow = 0 - rect_clamped.left;
if (overflow > 0)
rect_clamped.left += overflow;
overflow = 0 - rect_clamped.top;
if (overflow > 0)
rect_clamped.top += overflow;
overflow = rect_clamped.right - surface->width;
if (overflow > 0)
rect_clamped.right -= overflow;
overflow = rect_clamped.bottom - surface->height;
if (overflow > 0)
rect_clamped.bottom -= overflow;
if (rect_clamped.bottom - rect_clamped.top <= 0)
return;
if (rect_clamped.right - rect_clamped.left <= 0)
return;
for (long j = 0; j < rect_clamped.bottom - rect_clamped.top; ++j)
{
unsigned char *destination_pointer = &surface->pixels[((rect_clamped.top + j) * surface->pitch) + (rect_clamped.left * 4)];
for (long i = 0; i < rect_clamped.right - rect_clamped.left; ++i)
{
#ifdef LITTLE_ENDIAN
*destination_pointer++ = blue;
*destination_pointer++ = green;
*destination_pointer++ = red;
destination_pointer++;
#else
destination_pointer++;
*destination_pointer++ = blue;
*destination_pointer++ = green;
*destination_pointer++ = red;
#endif
}
}
}
RenderBackend_GlyphAtlas* RenderBackend_CreateGlyphAtlas(size_t width, size_t height)
{
RenderBackend_GlyphAtlas *atlas = (RenderBackend_GlyphAtlas*)malloc(sizeof(RenderBackend_GlyphAtlas));
if (atlas != NULL)
{
atlas->pixels = (unsigned char*)malloc(width * height);
if (atlas->pixels != NULL)
{
atlas->width = width;
atlas->height = height;
return atlas;
}
free(atlas);
}
return NULL;
}
void RenderBackend_DestroyGlyphAtlas(RenderBackend_GlyphAtlas *atlas)
{
free(atlas->pixels);
free(atlas);
}
void RenderBackend_UploadGlyph(RenderBackend_GlyphAtlas *atlas, size_t x, size_t y, const unsigned char *pixels, size_t width, size_t height, size_t pitch)
{
for (size_t i = 0; i < height; ++i) {
memcpy(&atlas->pixels[(y + i) * atlas->width + x], &pixels[i * pitch], width);
}
}
void RenderBackend_PrepareToDrawGlyphs(RenderBackend_GlyphAtlas *atlas, RenderBackend_Surface *destination_surface, unsigned char red, unsigned char green, unsigned char blue)
{
glyph_atlas = atlas;
glyph_destination_surface = destination_surface;
glyph_colour_channels[0] = red;
glyph_colour_channels[1] = green;
glyph_colour_channels[2] = blue;
}
void RenderBackend_DrawGlyph(long x, long y, size_t glyph_x, size_t glyph_y, size_t glyph_width, size_t glyph_height)
{
size_t surface_x;
size_t surface_y;
// Clamp to within the destination surface's boundaries
if (x < 0)
{
surface_x = -x;
if (surface_x >= glyph_width)
return; // Glyph is offscreen to the left
glyph_x += surface_x;
glyph_width -= surface_x;
}
else
{
surface_x = x;
}
if (surface_x >= glyph_destination_surface->width)
return; // Glyph is offscreen to the right
if (glyph_width >= glyph_destination_surface->width - surface_x)
glyph_width = glyph_destination_surface->width - surface_x;
if (y < 0)
{
surface_y = -y;
if (surface_y >= glyph_height)
return; // Glyph is offscreen to the top
glyph_y += surface_y;
glyph_height -= surface_y;
}
else
{
surface_y = y;
}
if (surface_y >= glyph_destination_surface->height)
return; // Glyph is offscreen to the bottom
if (glyph_height >= glyph_destination_surface->height - surface_y)
glyph_height = glyph_destination_surface->height - surface_y;
// Do the actual drawing
for (size_t iy = 0; iy < glyph_height; ++iy)
{
for (size_t ix = 0; ix < glyph_width; ++ix)
{
const unsigned char alpha_int = glyph_atlas->pixels[(glyph_y + iy) * glyph_atlas->width + (glyph_x + ix)];
if (alpha_int != 0)
{
const float alpha = alpha_int / 255.0f;
unsigned char *bitmap_pixel = &glyph_destination_surface->pixels[(surface_y + iy) * glyph_destination_surface->pitch + (surface_x + ix) * 4];
for (unsigned int j = 0; j < 3; ++j)
bitmap_pixel[j] = (unsigned char)((glyph_colour_channels[j] * alpha) + (bitmap_pixel[j] * (1.0f - alpha))); // Alpha blending
}
}
}
}
void RenderBackend_HandleRenderTargetLoss(void)
{
// No problem for us
}
void RenderBackend_HandleWindowResize(size_t width, size_t height)
{
WindowBackend_Software_HandleWindowResize(width, height);
}