Deno SDL2 Default Text Render Example (FFI problem w 1.29)

Trying to get the font render example in the Deno SDL2 repo https://github.com/littledivy/deno_sdl2/blob/main/examples/font/font.ts to work but it perpetually complains about a pointer to buffer issue. I've read through the issues that were proposed to fix this solution and tried modifications of my own to solve this issue based on this PR here https://github.com/littledivy/deno_sdl2/pull/58 but the issue still remained in my own modifications. Wondering if anybody has any ideas on how to resolve this as it pertains specifically to FFI problems which I thought would be resolved in Deno 1.29 since that seemed to caused breaking changes. I've already tried the v3 released of SDL2 and it did not resolve the problem either. I have a large and mature project I want to port over to desktop but cannot use this solution until I can draw font. I am not seeking alternative solutions such as Electron :) Error:
Uncaught TypeError: Invalid FFI buffer type, expected null, ArrayBuffer, or ArrayBufferView
const raw = sdl2Font.symbols.TTF_RenderText_Solid(
Uncaught TypeError: Invalid FFI buffer type, expected null, ArrayBuffer, or ArrayBufferView
const raw = sdl2Font.symbols.TTF_RenderText_Solid(
GitHub
deno_sdl2/font.ts at main · littledivy/deno_sdl2
SDL2 module for Deno. Contribute to littledivy/deno_sdl2 development by creating an account on GitHub.
17 Replies
AapoAlas
AapoAlas2y ago
What's the exact error?
captainbuckkets
Forgot to include: @aapoalas
Uncaught TypeError: Invalid FFI buffer type, expected null, ArrayBuffer, or ArrayBufferView
const raw = sdl2Font.symbols.TTF_RenderText_Solid(
Uncaught TypeError: Invalid FFI buffer type, expected null, ArrayBuffer, or ArrayBufferView
const raw = sdl2Font.symbols.TTF_RenderText_Solid(
captainbuckkets
You can read a similar error here which could be resolved by changing the expected parameter to "buffer" instead of "pointer" but making those changes for the font render doesn't resolve the error for me https://github.com/littledivy/deno_sdl2/issues/55
GitHub
Uncaught TypeError: Invalid FFI pointer type, expected null, intege...
Trying to run the hello.ts example, but getting an error, pasted below. This happens on multiple machines on the latest deno. The most recent version of deno I have laying around where the code wor...
captainbuckkets
This would be the code I'm working out of
import { Color, EventType, WindowBuilder } from "./old_mod.ts";
import { FPS } from "./utils.ts";

const stepFrame = FPS();
const window = new WindowBuilder("deno_sdl2 Font", 800, 600).build();
const canvas = window.canvas();

const font = canvas.loadFont(".\\jetbrains-mono.ttf", 128);
const color = new Color(0, 0, 0);

const surface = font.renderSolid("Hello there!", color);

const creator = canvas.textureCreator();
const texture = creator.createTextureFromSurface(surface);

async function frame() {
canvas.clear();
canvas.copy(texture);
canvas.present();
stepFrame();
}

for (const event of window.events()) {
if (event.type == EventType.Quit) Deno.exit(0);
else if (event.type == EventType.Draw) frame();
}
import { Color, EventType, WindowBuilder } from "./old_mod.ts";
import { FPS } from "./utils.ts";

const stepFrame = FPS();
const window = new WindowBuilder("deno_sdl2 Font", 800, 600).build();
const canvas = window.canvas();

const font = canvas.loadFont(".\\jetbrains-mono.ttf", 128);
const color = new Color(0, 0, 0);

const surface = font.renderSolid("Hello there!", color);

const creator = canvas.textureCreator();
const texture = creator.createTextureFromSurface(surface);

async function frame() {
canvas.clear();
canvas.copy(texture);
canvas.present();
stepFrame();
}

for (const event of window.events()) {
if (event.type == EventType.Quit) Deno.exit(0);
else if (event.type == EventType.Draw) frame();
}
const sdl2Font = Deno.dlopen(getLibraryPath("SDL2_ttf"), {
"TTF_Init": {...},
"TTF_OpenFont": {...},
"TTF_RenderText_Solid": {
"parameters": ["buffer", "buffer", "buffer"],
"result": "pointer",
},
const sdl2Font = Deno.dlopen(getLibraryPath("SDL2_ttf"), {
"TTF_Init": {...},
"TTF_OpenFont": {...},
"TTF_RenderText_Solid": {
"parameters": ["buffer", "buffer", "buffer"],
"result": "pointer",
},
You can see the relevant section its failing on here
AapoAlas
AapoAlas2y ago
Catch the error, check parameters. It seems like the code is probably being called with a pointer number somewhere.
captainbuckkets
Okay, I wrapped it in try catch and added additional logging around the function it goes into
export class Font {
[_raw]: Deno.UnsafePointer;
constructor(raw: Deno.UnsafePointer) {
this[_raw] = raw;
}

renderSolid(text: string, color: Color) {
try {
console.log("text", text, "color", color)
console.log(
"this[_raw]", this[_raw],
"asCString(text)", asCString(text),
"color[_raw]", color[_raw],
)
const raw = sdl2Font.symbols.TTF_RenderText_Solid(
this[_raw],
asCString(text),
color[_raw],
);
return new Texture(raw);
} catch (error) {
console.log("error", error)
}
}
...
}
export class Font {
[_raw]: Deno.UnsafePointer;
constructor(raw: Deno.UnsafePointer) {
this[_raw] = raw;
}

renderSolid(text: string, color: Color) {
try {
console.log("text", text, "color", color)
console.log(
"this[_raw]", this[_raw],
"asCString(text)", asCString(text),
"color[_raw]", color[_raw],
)
const raw = sdl2Font.symbols.TTF_RenderText_Solid(
this[_raw],
asCString(text),
color[_raw],
);
return new Texture(raw);
} catch (error) {
console.log("error", error)
}
}
...
}
// Console.log output
text Hello there! color Color { [Symbol(raw)]: 2643565591888 }
this[_raw] 2644682958720 asCString(text) Uint8Array(13) [
72, 101, 108, 108, 111,
32, 116, 104, 101, 114,
101, 33, 0
] color[_raw] 2643565591888
// Console.log output
text Hello there! color Color { [Symbol(raw)]: 2643565591888 }
this[_raw] 2644682958720 asCString(text) Uint8Array(13) [
72, 101, 108, 108, 111,
32, 116, 104, 101, 114,
101, 33, 0
] color[_raw] 2643565591888
How can I identify a pointer in JS? Must be the this[_raw] since thats a Deno.UnsafePointer I tried to cast it as an array buffer but it said allocation failed
renderSolid(text: string, color: Color) {
try {
console.log("text", text, "color", color)
console.log(
"this[_raw]", this[_raw],
"asCString(text)", asCString(text),
"color[_raw]", color[_raw],
)
const pointer = this[_raw];
const buffer = new ArrayBuffer(pointer);
const raw = sdl2Font.symbols.TTF_RenderText_Solid(
buffer,
asCString(text),
color[_raw],
);
return new Texture(raw);
} catch (error) {
console.log("error", error)
}
}
renderSolid(text: string, color: Color) {
try {
console.log("text", text, "color", color)
console.log(
"this[_raw]", this[_raw],
"asCString(text)", asCString(text),
"color[_raw]", color[_raw],
)
const pointer = this[_raw];
const buffer = new ArrayBuffer(pointer);
const raw = sdl2Font.symbols.TTF_RenderText_Solid(
buffer,
asCString(text),
color[_raw],
);
return new Texture(raw);
} catch (error) {
console.log("error", error)
}
}
Error
error RangeError: Array buffer allocation failed
error RangeError: Array buffer allocation failed
Very bad because that means it cant allocate enough Could be either the this[_raw] or the Color since that might be being passed in as a pointer. Casting both using the asCString did not fix, nor did changing the type expectations in the relevant section
captainbuckkets
Tried casting color and the pointer to BigInt and tried using TextEncoder but no change https://stackoverflow.com/questions/61813646/whats-deno-equivalent-of-node-js-buffer-fromstring
Stack Overflow
What's Deno equivalent of Node.js Buffer.from(string)
How can I convert a string to a buffer? I tried: Uint8Array.from('hello world') but it isn't working
captainbuckkets
I've seen this so many I can try to adjust it
captainbuckkets
I've changed every value in here to null and identified the text as the problem it seems
const raw = sdl2Font.symbols.TTF_RenderText_Solid(
this[_raw],
null, // asCString(text)
color[_raw],
);
const raw = sdl2Font.symbols.TTF_RenderText_Solid(
this[_raw],
null, // asCString(text)
color[_raw],
);
This will atleast not result in an error Okay so this
renderSolid(text: string, color: Color) {
try {
const encoder = new TextEncoder()
const buffer = encoder.encode(text);
const raw = sdl2Font.symbols.TTF_RenderText_Solid(
this[_raw],
buffer,
color[_raw],
);
return new Texture(raw);
} catch (error) {
console.log("error", error)
}
}
renderSolid(text: string, color: Color) {
try {
const encoder = new TextEncoder()
const buffer = encoder.encode(text);
const raw = sdl2Font.symbols.TTF_RenderText_Solid(
this[_raw],
buffer,
color[_raw],
);
return new Texture(raw);
} catch (error) {
console.log("error", error)
}
}
with
"TTF_RenderText_Solid": {
"parameters": ["pointer", "buffer", "pointer"],
"result": "pointer",
},
"TTF_RenderText_Solid": {
"parameters": ["pointer", "buffer", "pointer"],
"result": "pointer",
},
is acceptable and doesn't error out
captainbuckkets
Now just to figure out how to draw a rect in SDL and copy the texture to it so its predictable
AapoAlas
AapoAlas2y ago
Good that you got it figured out. The logging here shows that the first and third parameters are indeed numbers (pointer) and not buffers, so that was the issue as you found out. The asCstring method will probably automatically add a null character at the end of your string, whereas doing a direct encode() does not and will probably eventually lead to the native code reading beyond the bounds of your string buffer. Better to use the asCstring helper method.
captainbuckkets
I did the encode and it did remove the null character but I see that as a serious concern. How then, can one draw font without those characters in a cross platform way? Also it did kinda seem that playing with the encoding of the test made the color application not stick to the font consistently
AapoAlas
AapoAlas2y ago
By adding the null character. Encode '${string}\0' or just use the asCstring helper which does that for you
captainbuckkets
Interesting. Will try again tomorrow and see what it does
AapoAlas
AapoAlas2y ago
It will essentially just make the resulting Uint8Array 1 byte longer. eg. above your "Hello there!" logged:
asCString(text) Uint8Array(13) [
72, 101, 108, 108, 111,
32, 116, 104, 101, 114,
101, 33, 0
]
asCString(text) Uint8Array(13) [
72, 101, 108, 108, 111,
32, 116, 104, 101, 114,
101, 33, 0
]
With a direct encode it'd log
encoder.encode(text) Uint8Array(12) [
72, 101, 108, 108, 111,
32, 116, 104, 101, 114,
101, 33
]
encoder.encode(text) Uint8Array(12) [
72, 101, 108, 108, 111,
32, 116, 104, 101, 114,
101, 33
]
So, you're just missing the singlar null byte from the end.
captainbuckkets
I see. That seemed to resolve it. Still have inconsistent color results which is really odd