SengokuRanceFont: anti-aliasing and glyph caching

This improves the look and (especially) the performance of text
rendering in the MangaGamer version of Sengoku Rance.

There are still some bugs/unimplemented features, but the game should be
more or less bearable to play in this state.
This commit is contained in:
Nunuhara Cabbage
2022-04-24 17:19:27 -07:00
parent 3b72b88e9c
commit 8fb0153963
7 changed files with 203 additions and 144 deletions
+5 -1
View File
@@ -25,6 +25,7 @@
struct cg;
struct text_metrics;
enum cg_type;
enum draw_method {
DRAW_METHOD_NORMAL,
@@ -88,12 +89,14 @@ void gfx_init_texture_with_cg(struct texture *t, struct cg *cg);
void gfx_init_texture_rgba(struct texture *t, int w, int h, SDL_Color color);
void gfx_init_texture_rgb(struct texture *t, int w, int h, SDL_Color color);
void gfx_init_texture_with_pixels(struct texture *t, int w, int h, void *pixels);
void gfx_init_texture_amap(struct texture *t, int w, int h, uint8_t *amap, SDL_Color color);
void gfx_copy_main_surface(struct texture *dst);
void gfx_delete_texture(struct texture *t);
GLuint gfx_set_framebuffer(GLenum target, Texture *t, int x, int y, int w, int h);
void gfx_reset_framebuffer(GLenum target, GLuint fbo);
SDL_Color gfx_get_pixel(Texture *t, int x, int y);
void *gfx_get_pixels(Texture *t);
int gfx_save_texture(Texture *t, const char *path, enum cg_type);
// drawing
void gfx_draw_init(void);
@@ -224,7 +227,8 @@ struct text_style {
struct fnl;
struct fnl_font_face;
int fnl_draw_text(struct fnl *fnl, struct text_style *ts, Texture *dst, int x, int y, char *text);
float fnl_draw_text(struct fnl *fnl, struct text_style *ts, Texture *dst, float x, int y, char *text);
float fnl_size_text(struct fnl *fnl, struct text_style *ts, char *text);
void fnl_renderer_free(struct fnl *fnl);
#endif /* SYSTEM4_SDL_CORE_H */
+2
View File
@@ -104,4 +104,6 @@ bool sprite_is_point_in_rect(struct sact_sprite *sp, int x, int y);
int sprite_get_amap_value(struct sact_sprite *sp, int x, int y);
void sprite_get_pixel_value(struct sact_sprite *sp, int x, int y, int *r, int *g, int *b);
void print_sprite(struct sact_sprite *sp);
#endif /* SYSTEM4_SPRITE_H */
+90 -114
View File
@@ -14,6 +14,8 @@
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <math.h>
#include "gfx/gfx.h"
#include "gfx/types.h"
#include "system4.h"
@@ -21,110 +23,16 @@
#include "system4/utfsjis.h"
#include "gfx/gfx.h"
static void set_pixel(uint8_t *p, int w, int h, int x, int y, SDL_Color c)
static void draw_glyph(Texture *dst, float x, int y, Texture *glyph, float scale_x, float scale_y)
{
if (x >= w || y >= h)
return;
p[y*w*4 + x*4 + 0] = c.r;
p[y*w*4 + x*4 + 1] = c.g;
p[y*w*4 + x*4 + 2] = c.b;
p[y*w*4 + x*4 + 3] = 255;
}
static void render_outline(struct text_style *ts, uint8_t *surf, uint8_t *data, unsigned long data_size, int x, int w, int h)
{
const int span = w * 4;
const int glyph_w = (data_size*8) / h;
const int size = ts->font_size->denominator * ts->edge_width;
uint32_t p = 0;
for (int row = h - 1; row >= 0; row--) {
uint8_t *pos = surf + row*span + x*4;
for (int col = 0; col < glyph_w; col++, p++, pos += 4) {
bool on = data[p/8] & (1 << (7 - p % 8));
if (!on)
continue;
int start_x = x + col - size;
int start_y = row - size;
for (int ol_row = 0; ol_row < size*2; ol_row++) {
for (int ol_col = 0; ol_col < size*2; ol_col++) {
set_pixel(surf, w, h, start_x + ol_col, start_y + ol_row, ts->edge_color);
}
}
}
}
}
// TODO: cache glyph textures, render directly to dst texture
static void render_glyph(struct fnl_font_face *font, struct text_style *ts, uint8_t *surf, struct fnl_glyph *glyph, int x, int w, int h)
{
unsigned long data_size;
uint8_t *data = fnl_glyph_data(font->font->fnl, glyph, &data_size);
const int span = w * 4;
const int glyph_w = (data_size*8) / h;
if (ts->edge_width)
render_outline(ts, surf, data, data_size, x, w, h);
uint32_t p = 0;
for (int row = h - 1; row >= 0; row--) {
uint8_t *pos = surf + row*span + x*4;
for (int col = 0; col < glyph_w; col++, p++) {
bool on = data[p/8] & (1 << (7 - p % 8));
if (on) {
set_pixel(surf, w, h, x+col, row, ts->color);
}
pos += 4;
}
}
free(data);
}
static void render_text(struct fnl_font_face *font, struct text_style *ts, Texture *dst, char *text)
{
// create array of fnl_glyph
const int height = font->height;
int len = sjis_count_char(text);
struct fnl_glyph **glyphs = xcalloc(len, sizeof(struct fnl_glyph*));
for (int i = 0; i < len; i++) {
glyphs[i] = fnl_get_glyph(font, sjis_code(text));
text = sjis_skip_char(text);
}
// calculate width/height of text surface
int width = 0;
for (int i = 0; i < len; i++) {
width += glyphs[i]->real_width;
}
// allocate memory for surface
uint8_t *surf = xcalloc(4, width * height);
// render glyphs to surface
int x = 0;
for (int i = 0; i < len; i++) {
render_glyph(font, ts, surf, glyphs[i], x, width, height);
x += glyphs[i]->real_width;
}
// create texture from surface
gfx_init_texture_with_pixels(dst, width, height, surf);
free(surf);
free(glyphs);
}
static void draw_text(Texture *dst, Texture *glyph, int x, int y, float scale_x, float scale_y)
{
GLuint fbo = gfx_set_framebuffer(GL_DRAW_FRAMEBUFFER, dst, x, y, glyph->w, glyph->h);
GLuint fbo = gfx_set_framebuffer(GL_DRAW_FRAMEBUFFER, dst, roundf(x), y, glyph->w, glyph->h);
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
mat4 mw_transform = MAT4(
glyph->w * scale_x, 0, 0, 0,
0, glyph->h * scale_y, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
glyph->w * scale_x, 0, 0, 0,
0, glyph->h * scale_y, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
mat4 wv_transform = WV_TRANSFORM(glyph->w, glyph->h);
struct gfx_render_job job = {
.texture = glyph->handle,
@@ -134,29 +42,84 @@ static void draw_text(Texture *dst, Texture *glyph, int x, int y, float scale_x,
};
gfx_render(&job);
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
gfx_reset_framebuffer(GL_DRAW_FRAMEBUFFER, fbo);
}
int fnl_draw_text(struct fnl *fnl, struct text_style *ts, Texture *dst, int x, int y, char *text)
struct fnl_glyph_texture {
Texture t;
SDL_Color c;
};
enum glyph_texture_type {
FONT_TEXTURE = 0,
EDGE_TEXTURE = 1
};
static Texture *get_glyph_texture(struct fnl_rendered_glyph *glyph, SDL_Color *c,
enum glyph_texture_type type)
{
Texture rendered;
struct fnl_font_face *font = &fnl->fonts[ts->font_type].faces[ts->font_size->face];
render_text(font, ts, &rendered, text);
if (!glyph->data) {
glyph->data = xcalloc(2, sizeof(struct fnl_glyph_texture));
}
glBlendEquationSeparate(GL_FUNC_ADD, GL_MAX);
float scale_y = 1.0 / ts->font_size->denominator;
float scale_x = scale_y * ts->scale_x;
draw_text(dst, &rendered, x, y, scale_x, scale_y);
glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD);
struct fnl_glyph_texture *g = glyph->data;
g += type;
int w = rendered.w * scale_x;
gfx_delete_texture(&rendered);
return w;
if (!g->t.handle) {
gfx_init_texture_amap(&g->t, glyph->width, glyph->height, glyph->pixels, *c);
g->c = *c;
return &g->t;
}
if (g->c.r != c->r || g->c.g != c->g || g->c.b != c->b) {
gfx_fill(&g->t, 0, 0, g->t.w, g->t.h, c->r, c->g, c->b);
g->c = *c;
}
return &g->t;
}
static void draw_glyph_outline(Texture *dst, float x, int y, Texture *glyph, struct text_style *ts)
{
float edge_width = roundf(ts->edge_width);
draw_glyph(dst, x-edge_width, y, glyph, ts->scale_x, 1.0);
draw_glyph(dst, x+edge_width, y, glyph, ts->scale_x, 1.0);
draw_glyph(dst, x, y-edge_width, glyph, ts->scale_x, 1.0);
draw_glyph(dst, x, y+edge_width, glyph, ts->scale_x, 1.0);
}
float fnl_draw_text(struct fnl *fnl, struct text_style *ts, Texture *dst, float x, int y, char *text)
{
int len = sjis_count_char(text);
struct fnl_rendered_glyph **glyphs = xmalloc(len * sizeof(struct fnl_rendered_glyph*));
for (int i = 0; i < len; i++) {
glyphs[i] = fnl_render_glyph(ts->font_size, sjis_code(text));
text = sjis_skip_char(text);
}
float original_x = x;
if (ts->edge_width > 0.5) {
for (int i = 0; i < len; i++) {
Texture *t = get_glyph_texture(glyphs[i], &ts->edge_color, EDGE_TEXTURE);
draw_glyph_outline(dst, x, y, t, ts);
x += glyphs[i]->advance * ts->scale_x;
}
}
x = original_x;
for (int i = 0; i < len; i++) {
Texture *t = get_glyph_texture(glyphs[i], &ts->color, FONT_TEXTURE);
draw_glyph(dst, x, y, t, ts->scale_x, 1.0);
x += glyphs[i]->advance * ts->scale_x;
}
free(glyphs);
return x - original_x;
}
float fnl_size_text(struct fnl *fnl, struct text_style *ts, char *text)
{
struct fnl_font_face *font = &fnl->fonts[ts->font_type].faces[ts->font_size->face];
struct fnl_font_face *font = ts->font_size->face;
int width = 0;
int len = sjis_count_char(text);
for (int i = 0; i < len; i++) {
@@ -166,3 +129,16 @@ float fnl_size_text(struct fnl *fnl, struct text_style *ts, char *text)
return ((float)width / (float)ts->font_size->denominator) * ts->scale_x;
}
void free_textures(void *data)
{
struct fnl_glyph_texture *textures = data;
gfx_delete_texture(&textures[FONT_TEXTURE].t);
gfx_delete_texture(&textures[EDGE_TEXTURE].t);
free(textures);
}
void fnl_renderer_free(struct fnl *fnl)
{
fnl_free(fnl, free_textures);
}
+1 -3
View File
@@ -384,8 +384,6 @@ static void SengokuRanceFont_SP_TextDrawClassic(int sp_no, struct string *text)
struct sr_text_properties *p = get_sp_properties(sp_no);
int y = p->y - p->ts.size * 0.1875;
p->x += sp_text_draw(sp, &p->ts, text, p->x, y);
WARNING("\"%s\"", text->text);
}
HLL_WARN_UNIMPLEMENTED( , void, SengokuRanceFont, SP_TextDrawPreload, int sp_no, struct string *text);
@@ -537,7 +535,7 @@ static void SengokuRanceFont_ModuleInit(void)
static void SengokuRanceFont_ModuleFini(void)
{
fnl_free(fontlib);
fnl_renderer_free(fontlib);
}
HLL_LIBRARY(SengokuRanceFont,
+64 -25
View File
@@ -148,31 +148,7 @@ int sprite_set_cg_from_file(struct sact_sprite *sp, const char *path)
int sprite_save_cg(struct sact_sprite *sp, const char *path)
{
void *pixels = gfx_get_pixels(sprite_get_texture(sp));
struct cg cg = {
.type = ALCG_UNKNOWN,
.metrics = {
.w = sp->rect.w,
.h = sp->rect.h,
.bpp = 24,
.has_pixel = sp->sp.has_pixel,
.has_alpha = sp->sp.has_alpha,
.pixel_pitch = sp->rect.w * 3,
.alpha_pitch = 1
},
.pixels = pixels
};
FILE *fp = file_open_utf8(path, "wb");
if (!fp) {
WARNING("Failed to open %s: %s", display_utf0(path), strerror(errno));
free(pixels);
return 0;
}
int r = cg_write(&cg, ALCG_QNT, fp);
fclose(fp);
free(pixels);
return r;
return gfx_save_texture(sprite_get_texture(sp), path, ALCG_QNT);
}
/*
@@ -337,3 +313,66 @@ void sprite_get_pixel_value(struct sact_sprite *sp, int x, int y, int *r, int *g
*g = c.g;
*b = c.b;
}
static void print_color(SDL_Color *c)
{
printf("(%d,%d,%d,%d)", c->r, c->g, c->b, c->a);
}
static void print_rectangle(Rectangle *r)
{
printf("{x=%d,y=%d,w=%d,h=%d}", r->x, r->y, r->w, r->h);
}
static void print_point(Point *p)
{
printf("{x=%d,y=%d}", p->x, p->y);
}
static void print_texture(struct texture *t, int indent)
{
puts("{");
for (int i = 0; i < indent+1; i++) putchar('\t');
printf("initialized = %s,\n", t->handle ? "true" : "false");
for (int i = 0; i < indent+1; i++) putchar('\t');
printf("size = (%d,%d),\n", t->w, t->h);
for (int i = 0; i < indent+1; i++) putchar('\t');
printf("has_alpha = %s,\n", t->has_alpha ? "true" : "false");
for (int i = 0; i < indent+1; i++) putchar('\t');
printf("alpha_mod = %d,\n", t->alpha_mod);
for (int i = 0; i < indent+1; i++) putchar('\t');
switch (t->draw_method) {
case DRAW_METHOD_NORMAL: printf("draw_method = normal\n"); break;
case DRAW_METHOD_SCREEN: printf("draw_method = screen\n"); break;
case DRAW_METHOD_MULTIPLY: printf("draw_method = multiply\n"); break;
case DRAW_METHOD_ADDITIVE: printf("draw_method = additive\n"); break;
default: printf("draw_method = unknown\n"); break;
}
for (int i = 0; i < indent; i++) putchar('\t');
putchar('}');
}
void print_sprite(struct sact_sprite *sp)
{
printf("sprite %d = {\n", sp->no);
printf("\tsp = {\n");
printf("\t\tz = (%d,%d),\n", sp->sp.z, sp->sp.z2);
printf("\t\thas_pixel = %s,\n", sp->sp.has_pixel ? "true" : "false");
printf("\t\thas_alpha = %s,\n", sp->sp.has_alpha ? "true" : "false");
printf("\t\thidden = %s,\n", sp->sp.hidden ? "true" : "false");
printf("\t\tin_scene = %s,\n", sp->sp.in_scene ? "true" : "false");
printf("\t},\n");
printf("\ttexture = "); print_texture(&sp->texture, 1); printf(",\n");
printf("\tcolor = "); print_color(&sp->color); printf(",\n");
printf("\trect = "); print_rectangle(&sp->rect); printf(",\n");
printf("\ttext = {\n");
//printf("\t\tstring = \"%s\",\n", display_sjis0(sp->text.str->text));
printf("\t\ttexture = "); print_texture(&sp->text.texture, 2); printf(",\n");
printf("\t\thome = "); print_point(&sp->text.home); printf(",\n");
printf("\t\tpos = "); print_point(&sp->text.pos); printf(",\n");
printf("\t\tchar_space = %d,\n", sp->text.char_space);
printf("\t\tline_space = %d,\n", sp->text.line_space);
printf("\t},\n");
printf("\tcg_no = %d\n", sp->cg_no);
printf("}\n");
}
+40
View File
@@ -516,6 +516,18 @@ void gfx_init_texture_rgb(struct texture *t, int w, int h, SDL_Color color)
free(pixels);
}
void gfx_init_texture_amap(struct texture *t, int w, int h, uint8_t *amap, SDL_Color color)
{
uint32_t *pixels = xmalloc(sizeof(uint32_t) * w * h);
for (int i = 0; i < w*h; i++) {
uint32_t c = SDL_MapRGBA(sdl.format, color.r, color.g, color.b, amap[i]);
pixels[i] = c;
}
gfx_init_texture_with_pixels(t, w, h, pixels);
free(pixels);
}
void gfx_init_texture_blank(struct texture *t, int w, int h)
{
gfx_init_texture_with_pixels(t, w, h, NULL);
@@ -579,3 +591,31 @@ void *gfx_get_pixels(Texture *t)
gfx_reset_framebuffer(GL_READ_FRAMEBUFFER, fbo);
return pixels;
}
int gfx_save_texture(Texture *t, const char *path, enum cg_type format)
{
void *pixels = gfx_get_pixels(t);
struct cg cg = {
.type = ALCG_UNKNOWN,
.metrics = {
.w = t->w,
.h = t->h,
.bpp = 24,
.has_pixel = true,
.has_alpha = t->has_alpha,
.pixel_pitch = t->w * 3,
.alpha_pitch = 1
},
.pixels = pixels
};
FILE *fp = file_open_utf8(path, "wb");
if (!fp) {
WARNING("Failed to open %s: %s", display_utf0(path), strerror(errno));
free(pixels);
return 0;
}
int r = cg_write(&cg, format, fp);
fclose(fp);
free(pixels);
return r;
}