slint icon indicating copy to clipboard operation
slint copied to clipboard

Corrupt font using femtovg backend!

Open DanXS opened this issue 1 year ago • 2 comments

Hi Slint Team,

I'm working on an emulator/disassembler for the zx spectrum/z80 and I've noticed that when using Femtovg to render mono style fonts - which I need to use so that the alignment looks right.

It starts off ok but after a while corruption appears around the edges of some fonts and in areas which would normally be whitespace.

It works fine with winit-skia but not winit-femtovg backend, the only thing is I need OpenGL binding for the main simulation view and so I learned from your example's how to get hold of the openGL rendering context.

I'd be happy to move over to skia as it seems to perform slightly better when it was just the slint based UI without and customer rendering. I'm using the set_rendering_notifier() callback closure to manage the OpenGL context for the main view, it doesn't seem to exist when using skia, is there something equivalent should I want to abandon femtovg?

I'm using an fairly 2012 old macbook pro running Catalina which is the most modern Mac OS it will support unfortunately. I had a new iMac but it was stolen.

Here is a screenshot:

Corrupt_Font

I'm using these settings in my Cargo.toml file for rust:

[target.'cfg(target_os = "macos")'.dependencies]
# For GL rendering
cocoa = { version = "0.25.0" }

[dependencies]
slint = { version="1.3.2", default-features=false, features = ["std", "compat-1-2", "backend-winit", "renderer-femtovg"] }
glow = { version = "0.13" }

I'm not sure if the "macos" specific dependencies make any difference as I found them on a skia related example. I think on that it did seem faster with the cocoa option selected. Perhaps it uses metal?

Note that I had this issue with the fonts before I started any custom openGL code.

Apart from that I think it's a great product, I like to portability and simplicity. Perhaps a little more explanations on some of the deeper aspects would be good, but overall I really like using it, good job!

import "../../fonts/Menlo.ttc";

import { Button, ListView, HorizontalBox, ScrollView, LineEdit, ComboBox , CheckBox } from "std-widgets.slint";

export global Logic  {
    pure callback has_breakpoint(string) -> bool;
}

component DisplayView inherits Rectangle {
    in property <image> texture <=> image.source;
    out property <int> requested-texture-width: image.width/1phx;
    out property <int> requested-texture-height: image.height/1phx;
    width: 62.5%;
    height: self.width * (3.0/4.0);
    VerticalLayout {
        image := Image {
            preferred-width: 640px;
            preferred-height: 480px;
            min-width: 64px;
            min-height: 48px;
            width: 100%;
        }
    }
}

component MemoryView inherits Rectangle {
    in property <[string]> data;
    out property <string> addr_str;
    in property <int> scroll_row;
    callback scroll_to_addr(string);
    width: 62.5%;
    vertical-stretch: 1;
    background: #323232;
    VerticalLayout {
        padding: 2px;
        HorizontalBox {
            Text {
                font-size: 11pt;
                text: "Memory";
            }
            LineEdit {
                width: 60px;
                placeholder-text: "HEX";
                edited(val) => {
                    root.addr_str = val;
                }
                accepted(val) => {
                    root.addr_str = val;
                    root.scroll_to_addr(root.addr_str);
                    i-memory-list.viewport-x = 0;
                    i-memory-list.viewport-y = -(root.scroll_row)*30px;
                }
            }
            Button {
                width: 60px;
                text: "Goto";
                enabled: { (root.addr_str != "") };
                clicked => {
                    root.scroll_to_addr(root.addr_str);
                    i-memory-list.viewport-x = 0;
                    i-memory-list.viewport-y = -(root.scroll_row)*30px;
                }
            }
        }
        Rectangle {
            background: #1C1C1C;
            width: 100%;
            vertical-stretch: 1;
            i-memory-list := ListView {
                for txt in data
                : Rectangle {
                    height: 30px;
                    width: 630px;
                    Text {
                        x: 0;
                        text: txt;
                        font-size: 11pt;
                    }
                }
            }
        }
    }
}

component RegisterView inherits Rectangle {
    in property <[string]> data;
    in property <[string]> status_flag_names;
    in property <[bool]> status_flag_states;
    callback set_register(string, string);
    callback update_status_flag(string, bool);
    out property <string> reg_str;
    out property <string> val_str;
    width: 37.5%;
    height: 50%;
    background: #323232;
    VerticalLayout {
        padding:2px;
        HorizontalBox {
            Text {
                font-size: 11pt;
                text: "Registers";
            }
            ComboBox {
                width: 80px;
                model: ["", "AF", "AF'", "BC", "BC'", "DE", "DE'", "HL", "HL'", "IX", "IY", "SP", "PC", "IR", "WZ"];
                current-value: "";
                selected(reg) => {
                    root.reg_str = reg;
                }
            }
            LineEdit {
                width: 60px;
                placeholder-text: "HEX";
                enabled: {root.reg_str != ""};
                edited(val) => {
                    root.val_str = val;
                }
                accepted(val) => {
                    root.val_str = val;
                    root.set_register(root.reg_str, root.val_str);
                }
            }
            Button {
                width: 60px;
                text: "Set";
                enabled: { (root.reg_str != "") && (root.val_str != "") };
                clicked => {
                    root.set_register(root.reg_str, root.val_str);
                }
            }
        }
        Rectangle {
            background: #1C1C1C;
            width: 100%;
            vertical-stretch: 1;
            HorizontalLayout {
                ListView {
                    horizontal-stretch: 1;       
                    for txt in data
                    : Rectangle {
                        height: 30px;
                        width: parent.width;
                        Text {
                            x: 0;
                            text: txt;
                            font-size: 11pt;
                        }
                    }
                }
                Rectangle {
                    background: #323232;
                    width: 100px;
                    vertical-stretch: 1;
                    VerticalLayout {
                        padding-left:10px;
                        padding-top: 10px;
                        padding-bottom: 10px;
                        for i in status-flag-states.length
                        : CheckBox {
                            checked: status-flag-states[i];
                            text: status-flag-names[i];
                            toggled => {
                                update-status-flag(self.text, self.checked);
                            }
                        }
                        Rectangle {
                            background: #00000000;
                            vertical-stretch: 1;
                        }
                    }
                }
            }
        }
    }
}

component DisassemblyView inherits Rectangle {
    in property <[string]> data;
    in-out property <bool> is_running;
    in-out property <bool> enable_breakpoints;
    callback step;
    callback start_stop;
    callback toggle_breakpoint(string);
    callback toggle-enable-breakpoints();
    pure callback has_breakpoint <=> Logic.has_breakpoint;
    width: 37.5%;
    background: #323232;
    VerticalLayout {
        padding:2px;
        HorizontalBox {
            Text {
                font-size: 11pt;
                text: "Disassembly";
            }
            CheckBox {
                checked: enable_breakpoints;
                text: "BP";
                toggled => {
                    toggle-enable-breakpoints();
                }
            }
            Button {
                width: 60px;
                text : "Step";
                enabled: !is_running; 
                clicked => {
                    root.step();
                    i-disassembly-list-view.viewport-y = 0;
                }
            }
            Button {
                width: 60px;
                text: is_running ? "Stop" : "Run";
                clicked => {
                    root.start_stop();
                    i-disassembly-list-view.viewport-y = 0;
                }
            }
        }
        Rectangle {
            background: #1C1C1C;
            width: 100%;
            vertical-stretch: 1;
            i-disassembly-list-view := ListView {
                width: 100%;       
                for txt in data
                : Rectangle {
                    height: 30px;
                    background: {
                        enable-breakpoints ?
                        (has-breakpoint(txt) ? #ffcd68ff :
                            (txt == data[0]) ? #68cdff44 : #1C1C1C) :
                        (has-breakpoint(txt) ? #ffcd6844 :
                             (txt == data[0]) ? #68cdff44 : #1C1C1C)
                    };
                    width: parent.width;
                    Text {
                        x: 0;
                        text: txt;
                        font-size: 11pt;
                    }
                    touch := TouchArea {
                        clicked => {
                            toggle_breakpoint(txt);
                            parent.background = enable-breakpoints ?
                            (has-breakpoint(txt) ? #ffcd68ff :
                                (txt == data[0]) ? #68cdff44 : #1C1C1C) :
                            (has-breakpoint(txt) ? #ffcd6844 :
                                 (txt == data[0]) ? #68cdff44 : #1C1C1C)
                        }
                    }
                }
            }
        }
    }
}

export component MainWindow inherits Window {
    in property <image> texture;
    out property <int> requested-texture-width <=> displayview.requested-texture-width;
    out property <int> requested-texture-height <=> displayview.requested-texture-height;
    in property <[string]> memory_view_data;
    in property <[string]> register_view_data;
    in property <[bool]> status_view_data;
    in property <[string]> disassembly_view_data;
    in-out property <bool> enable_breakpoints;
    in-out property <bool> is_running;
    in property <int> memory_scroll_row;
    callback step;
    callback start_stop;
    callback set_register(string, string);
    callback update_status_flag(string, bool);
    callback memory_scroll_to_addr(string);
    callback toggle_breakpoint(string);
    callback toggle-enable-breakpoints();
    default-font-family: "Menlo";
    preferred-width: 1024px;
    preferred-height: 768px;
    min-width: 880px;
    min-height: 480px;
    title: "Z80 Emulator/Disassembler";
    HorizontalLayout {
        VerticalLayout {
            displayview := DisplayView {
                texture: texture;
            }
            MemoryView  {
                data: memory_view_data;
                scroll_row: memory_scroll_row;
                scroll_to_addr(addr) => {
                    root.memory_scroll_to_addr(addr);
                }
            }
        }
        VerticalLayout {
            RegisterView {
                data: register_view_data;
                status-flag-names: ["C","N","P/V","H","Z","S"];
                status-flag-states: status_view_data;
                set_register(reg, val) => {
                    root.set_register(reg, val);
                }
                update_status_flag(flag, val) => {
                    root.update_status_flag(flag, val);
                }
            }
            DisassemblyView {
                data: disassembly_view_data;
                is_running: is_running;
                enable_breakpoints: enable_breakpoints;
                toggle-enable-breakpoints() => {
                    root.toggle-enable-breakpoints();
                }
                step => {
                    root.step();
                }
                start_stop => {
                    root.start_stop();
                }
                toggle_breakpoint(line) => {
                    root.toggle_breakpoint(line);
                }
            }
        }
    }
}

DanXS avatar Feb 12 '24 04:02 DanXS

Thank you for the detailed bug report and the feedback - much appreciated. The rendering bug looks odd - I don't recall seeing this before. I'll try to reproduce it.

Regarding Skia and the rendering notifier: it should work if you enable the renderer-skia-opengl feature.

tronical avatar Feb 12 '24 05:02 tronical

Thank you for the detailed bug report and the feedback - much appreciated. The rendering bug looks odd - I don't recall seeing this before. I'll try to reproduce it.

Regarding Skia and the rendering notifier: it should work if you enable the renderer-skia-opengl feature.

Thanks I’ll try that

DanXS avatar Feb 12 '24 20:02 DanXS