Hello Guest

[SOLVED] EXCEPTION_ACCESS_VIOLATION when processing STBTTFontinfo object

  • 2 Replies
  • 4695 Views
I just got into text rendering with STB TrueType and I get consistent crashes.
The problem seems to be that the STBTTFontinfo object gets corrupted.

The way I try to manage Fonts is that I init every needed font in the initialization phase and store every STBTTFontinfo object in a HashMap.
When I need to create a bitmap for a codepoint (in the draw loop) I first fetch the STBTTFontinfo from the map and then process it. But at this time the object is already corrupted and the program crashes with an EXCEPTION_ACCESS_VIOLATION.


How can I stop the STBTTFontinfo from getting corrupted?
Am I somehow using this library incorrectly?


NOTE: The following code is just the Font-Manager class. If you need any code of me accessing it. I can provide it too :)
Code: [Select]
package com.game.graphics.render3d;

import com.util.ResourceLoader;
import org.lwjgl.BufferUtils;
import org.lwjgl.stb.STBTTFontinfo;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import static org.lwjgl.opengl.GL30.*;
import static org.lwjgl.stb.STBTruetype.stbtt_GetCodepointBitmap;
import static org.lwjgl.stb.STBTruetype.stbtt_GetCodepointHMetrics;
import static org.lwjgl.stb.STBTruetype.stbtt_GetFontNameString;
import static org.lwjgl.stb.STBTruetype.stbtt_GetFontVMetrics;
import static org.lwjgl.stb.STBTruetype.stbtt_InitFont;
import static org.lwjgl.stb.STBTruetype.stbtt_ScaleForPixelHeight;


public class Font {
public static final int PLAIN = 0b00;
public static final int BOLD = 0b01;
public static final int ITALIC = 0b10;


private static class FontInfoIdentifier {
public final String family;
public final int style;

public FontInfoIdentifier(String family, int style) {
this.family = family;
this.style = style;
}

@Override
public boolean equals(Object obj) {
if(obj == this) {
return true;
} else if(obj == null) {
return false;
} else if(!(obj instanceof FontInfoIdentifier)) {
return false;
} else {
FontInfoIdentifier fii = (FontInfoIdentifier) obj;
return family.equalsIgnoreCase(fii.family) && style == fii.style;
}
}

@Override
public int hashCode() {
return Objects.hash(family, style);
}
}



private static final Map<FontInfoIdentifier, STBTTFontinfo> fontInfoMap = new HashMap<>();
private static final Map<FontIdentifier, Font> fontMap = new HashMap<>();


private static String getFontNameString(STBTTFontinfo fontInfo, int nameID) {
ByteBuffer buf = stbtt_GetFontNameString(fontInfo, 3, 1, 0x0409, nameID);
byte[] bytes = new byte[buf.remaining()];
buf.get(bytes);
return new String(bytes, StandardCharsets.UTF_16);
}

private static STBTTFontinfo initFont(ByteBuffer data) {
ByteBuffer buffer = BufferUtils.createByteBuffer(STBTTFontinfo.SIZEOF);
STBTTFontinfo fontInfo = STBTTFontinfo.malloc();//create();
stbtt_InitFont(fontInfo, data);
return fontInfo;
}

private static FontInfoIdentifier createIdentifier(STBTTFontinfo fontInfo) {
String fontFamily = getFontNameString(fontInfo, 1);
String styleStr = getFontNameString(fontInfo, 2);
String[] styleSplit = styleStr.split(" ");

int style = PLAIN;
for(String s : styleSplit) {
if(s.equalsIgnoreCase("italic")) {
style |= ITALIC;
} else if(s.equalsIgnoreCase("bold")) {
style |= BOLD;
} else if(!s.equalsIgnoreCase("regular")) {
throw new RuntimeException("Unknown style modifier");
}
}

return new FontInfoIdentifier(fontFamily, style);
}

public static void loadFontFromFile(String file) throws IOException {
InputStream in = new FileInputStream(file);
byte[] bytes = in.readAllBytes();
ByteBuffer buffer = BufferUtils.createByteBuffer(bytes.length);
buffer.put(bytes);
buffer.flip();

STBTTFontinfo fontInfo = initFont(buffer);
fontInfoMap.put(createIdentifier(fontInfo), fontInfo);
}

public static void loadFontFromResource(String resource) throws IOException {
ByteBuffer buffer = ResourceLoader.loadByteBufferFromResource(resource);
STBTTFontinfo fontInfo = initFont(buffer);
fontInfoMap.put(createIdentifier(fontInfo), fontInfo);
}

public static Font getFont(FontIdentifier fontIdentifier) {
Font result = fontMap.get(fontIdentifier);
if(result == null) {
STBTTFontinfo fontInfo = fontInfoMap.get(new FontInfoIdentifier(fontIdentifier.family, fontIdentifier.style));
if(fontInfo == null) {
throw new RuntimeException("Font not loaded yet");
}

result = new Font(fontInfo, fontIdentifier.family, fontIdentifier.style, fontIdentifier.size);
fontMap.put(fontIdentifier, result);
}

return result;
}




public final STBTTFontinfo fontInfo;

private final String family;
private final int style;
private final int size;

public final float scale;

private final Map<Character, Glyph> glyphMap = new HashMap<>();

public Font(STBTTFontinfo fontInfo, String family, int style, int size) {
this.fontInfo = fontInfo;

this.family = family;
this.style = style;
this.size = size;

this.scale = stbtt_ScaleForPixelHeight(fontInfo, size);
}

public String getFamily() {
return this.family;
}

public int getStyle() {
return this.style;
}

public int getSize() {
return this.size;
}

public Glyph getCodepointGlyph(char codepoint) {
Glyph result = glyphMap.get(codepoint);
if(result == null) {
IntBuffer bufWidth = BufferUtils.createIntBuffer(1);
IntBuffer bufHeight = BufferUtils.createIntBuffer(1);
IntBuffer bufBearingX = BufferUtils.createIntBuffer(1);
IntBuffer bufAdvance = BufferUtils.createIntBuffer(1);
IntBuffer bufAscent = BufferUtils.createIntBuffer(1);
IntBuffer bufDescent = BufferUtils.createIntBuffer(1);
IntBuffer bufLineGap = BufferUtils.createIntBuffer(1);

stbtt_GetCodepointHMetrics(fontInfo, codepoint, bufAdvance, bufBearingX);
stbtt_GetFontVMetrics(fontInfo, bufAscent, bufDescent, bufLineGap);
ByteBuffer bitmap = stbtt_GetCodepointBitmap(fontInfo, 0, scale, codepoint, bufWidth, bufHeight, null, null);

int width = bufWidth.get();
int height = bufHeight.get();
int bearingX = bufBearingX.get();
int advance = bufAdvance.get();
int ascent = bufAscent.get();
int descent = bufDescent.get();
int lineGap = bufLineGap.get();


int textureID = glGenTextures();
glBindTexture(GL_TEXTURE_2D, textureID);

glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, bitmap);
glGenerateMipmap(GL_TEXTURE_2D);

result = new Glyph(textureID, width, height, bearingX, advance, ascent, descent, lineGap);
glyphMap.put(codepoint, result);
}

return result;
}
}
« Last Edit: July 02, 2019, 09:39:27 by Xen0moRtaX »

*

Offline spasi

  • *****
  • 2261
    • WebHotelier
Re: EXCEPTION_ACCESS_VIOLATION when processing STBTTFontinfo object
« Reply #1 on: July 02, 2019, 07:00:07 »
The STBTTFontinfo object contains metadata only. The actual font data is NOT copied from the ByteBuffer you pass to stbtt_InitFont. Only its memory address is copied and the font data is accessed via that pointer when necessary. The segfault you're seeing happens because you don't store a reference to the ByteBuffer anywhere, it is GCed/deallocated by the time you try to use the font. An easy fix would be to change your Font class to also store the ByteBuffer.

Re: EXCEPTION_ACCESS_VIOLATION when processing STBTTFontinfo object
« Reply #2 on: July 02, 2019, 09:38:32 »
The STBTTFontinfo object contains metadata only. The actual font data is NOT copied from the ByteBuffer you pass to stbtt_InitFont. Only its memory address is copied and the font data is accessed via that pointer when necessary. The segfault you're seeing happens because you don't store a reference to the ByteBuffer anywhere, it is GCed/deallocated by the time you try to use the font. An easy fix would be to change your Font class to also store the ByteBuffer.

Ah, that makes sense :-D
Thank you very much!