Giter VIP home page Giter VIP logo

skija's Introduction

Skija: Java bindings for Skia

Skia is an open source 2D graphics library which provides common APIs that work across a variety of hardware and software platforms.

Skija is a high-quality Java bindings for Skia.

Note: this is an active and supported branch of what was previously hosted at JetBrains/Skija. JetBrains/Skija is no longer updated.

Motivation: Why Skija?

Because Java doesn’t have a powerful and modern 2D graphics — still!

There are many occasions when you might need graphics:

  • custom UI widget libraries and whole toolkits,
  • graphs, diagrams,
  • visualizations,
  • games.

Existing offerings are: Graphics2D from AWT, GraphicsContext from JavaFX. They are good, but underwhelming.

Enter Skia. Skia has a proven track record of industrial-scale project relying on it for all things graphics: Google Chrome, Android, Flutter, Firefox Canvas, Xamarin, LibreOffice. Skia outperforms all existing Java2D toolkits in almost every benchmark, and also provides:

  • extensive color spaces support,
  • modern typography with open type features, variable typefaces, correct multi-script text handling, emojis,
  • highly-optimized GPU rendering,
  • modern GPU backends, including Vulkan and Metal (already in Skia, coming to Skija soon),
  • built-in caching and compositing facilities.

Skija brings all this power to the tips of your fingers, as simple as adding a jar to classpath.

Why hand-crafted bindings?

Automatically generated bindings for Skia exist, but don’t seem to have high adoption:

Skija project has a goal of providing great Java-native API that is natural to use. In particular:

  • full automatic memory management, no pointer abstractions leaking,
  • natural use of Java classes, interfaces, inheritance, singletons,
  • consistent naming following Java conventions, including getters/setters for properties,
  • typed enums instead of integer constants,
  • native Java platform abstractions instead of wrapped Skia/C++ ones (strings, arrays, streams, files, byte buffers, AutoCloseable),
  • hiding implementation details, e.g. transparent string encoding conversion, byte/code point indices conversion,
  • fluent builder-style APIs where possible,
  • lightweight data classes where possible (Point, Rect, FontMetrics, etc are not mirrored by native instances).

The ultimate goal for Skija is to feel as a Java library and not having to think about native part at all.

Built with Skija

Compose for Desktop, declarative UI toolkit for Kotlin:

Skija Graphics2D, an implementation of Java2D API:

Robert Felker demos:

Harold videos:

Skija demo app:

Current status

Public alpha. Things might change without notice.

Please note that Skia is known to change its API quite often (monthly). Skija will do its best to protect from these changes, but sometimes it might be impossible (e.g. method was removed). Normally it’s auxiliary stuff though, don’t worry too much.

Platforms:

  • Windows:
    • x64
    • arm64
  • Linux:
    • x64
    • arm64 (Experimental)
    • riscv64
  • macOS:
    • x64
    • arm64

Backends:

  • Bitmap
  • OpenGL
  • Direct3D
  • Metal
  • Vulkan

APIs:

Bitmap               ▓▓▓▓▓▓▓▓▓▓    Paint                ▓▓▓▓▓▓▓▓▓▓
Canvas               ▓▓▓▓▓▓▓▓░░    Path                 ▓▓▓▓▓▓▓▓▓▓
Codec                ▓▓▓▓░░░░░░    PathEffects          ▓▓▓▓▓▓▓▓▓▓
Color                ▓░░░░░░░░░    PathMeasure          ▓▓▓▓▓▓▓▓▓▓
ColorFilter          ▓▓▓▓▓▓▓▓▓▓    PaintFilterCanvas    ▓▓▓▓▓▓▓▓▓▓
ColorInfo            ▓▓▓▓▓▓▓▓▓▓    Picture              ▓▓▓▓▓▓▓▓▓░
ColorSpace           ▓▓▓▓░░░░░░    PictureRecorder      ▓▓▓▓▓▓▓▓▓▓
Data                 ▓▓▓▓▓▓▓▓▓░    PixelRef             ▓▓▓▓▓▓▓▓▓▓
Drawable             ▓▓▓▓▓▓▓▓░░    Pixmap               ▓▓▓▓▓▓▓▓▓▓
Flattenable          ░░░░░░░░░░    Region               ▓▓▓▓▓▓▓▓▓▓
Font                 ▓▓▓▓▓▓▓▓▓▓    RuntimeEffect        ▓▓▓▓▓░░░░░
FontData             ░░░░░░░░░░    ScalerContext        ░░░░░░░░░░
FontManager          ▓▓▓▓▓▓▓▓▓░    Shader               ▓▓▓▓▓▓▓▓▓▓
FontStyle            ▓▓▓▓▓▓▓▓▓▓    ShadowUtils          ▓▓▓▓▓▓▓▓▓▓
FontStyleSet         ▓▓▓▓▓▓▓▓▓▓    Stream               ░░░░░░░░░░
Image                ▓▓░░░░░░░░    String               ▓░░░░░░░░░
ImageFilters         ▓▓▓▓▓▓▓▓▓▓    Surface              ▓░░░░░░░░░
ImageInfo            ▓▓▓▓▓▓▓▓▓▓    TextBlob             ▓▓▓▓▓▓▓▓▓▓
MaskFilter           ▓▓▓▓▓▓▓▓▓▓    TextBlobBuilder      ▓▓▓▓▓▓▓▓▓▓
Matrix33             ▓▓▓░░░░░░░    Typeface             ▓▓▓▓▓▓▓▓░░
Matrix44             ▓▓▓░░░░░░░    WStream              ▓▓░░░░░░░░

Shaper:                            Paragraph:

BiDiRunIterator      ▓▓▓▓▓▓▓▓▓▓    FontCollection       ▓▓▓▓▓▓▓▓▓▓
FontMgrRunIterator   ▓▓▓▓▓▓▓▓▓▓    LineMetrics          ▓▓▓▓▓▓▓▓▓░
FontRunIterator      ▓▓▓▓▓▓▓▓▓▓    Paragraph            ▓▓▓▓▓▓▓▓▓▓
HbIcuScriptRunIter   ▓▓▓▓▓▓▓▓▓▓    ParagraphCache       ▓▓▓▓▓▓▓▓▓▓
IcuBidiRunIterator   ▓▓▓▓▓▓▓▓▓▓    ParagraphStyle       ▓▓▓▓▓▓▓▓▓▓
LanguageRunIterator  ▓▓▓▓▓▓▓▓▓▓    ParagraphBuilder     ▓▓▓▓▓▓▓▓▓▓
RunHandler           ▓▓▓▓▓▓▓▓▓▓    TextStyle            ▓▓▓▓▓▓▓▓▓▓
RunInfo              ▓▓▓▓▓▓▓▓▓▓    TypefaceFontProvider ▓▓▓▓▓▓▓▓▓▓
ScriptRunIterator    ▓▓▓▓▓▓▓▓▓▓
Shaper               ▓▓▓▓▓▓▓▓▓▓
TextBlobBldRunHndlr  ▓▓▓▓▓▓▓▓▓▓

SVG:

SVGDOM               ▓▓▓▓▓▓▓▓░░
SVGCanvas            ▓▓▓▓▓▓▓▓▓▓
SVGSVG               ▓▓▓▓▓▓▓▓░░

Using Skija

Using Skija is as simple as adding a jar file.

Maven:

<dependencies>
  <dependency>
    <groupId>io.github.humbleui</groupId>
    <artifactId>${artifact}</artifactId>
    <version>${version}</version>
  </dependency>
</dependencies>

Gradle:

dependencies {
  implementation("io.github.humbleui:${artifact}:${version}")
}

Replace ${artifact} and ${version} with:

Platform ${artifact} ${version}
Windows (x64) skija-windows-x64
Linux (x64) skija-linux-x64
Linux (arm64) skija-linux-arm64
macOS (x64) skija-macos-x64
macOS (arm64) skija-macos-arm64

For simplicity or if your build system is not smart enough to select artifact based on OS, you can add all you need as dependencies—they will not conflict.

Documentation

Get started by reading Getting Started.

API docs are under development — consult source code and JavaDoc comments.

I found SkiaSharp documentation to be excellent resource on what can be done in Skia. They have nice examples and visual explanations, too.

If Skija is missing a documentation for a particular method or class, check the same class in Skia Documentation instead. It might be that we didn’t move it over to Java yet. PRs are welcome!

Finally, LWJGL demo app has examples of most of the APIs that are currently implemented.

Resources

Building Skija from scratch

Prerequisites: Git, CMake, Ninja, JDK 9+, $JAVA_HOME, Python 3.

Checkout:

git clone https://github.com/HumbleUI/Skija.git
cd skija
./script/build.py

To codesign:

security find-identity
export APPLE_CODESIGN_IDENTITY="<...>"
./script/build.py

For building Skia itself, see github.com/HumbleUI/SkiaBuild

Running examples

Examples require local build of Skija (see Building Skija).

See examples/.

Contributing

Contributions are welcome!

To open the project in IntelliJ IDEA, please use the script to generate the IntelliJ IDEA project files first:

./script/idea.py

Make sure to read Code Conventions.

Remember: the goal of Skija is to map Skia API as close as possible.

These things have a place in Skija:

  • Everything that is in Skia and makes sense in Java world.
  • Convenience methods (e.g. a method that uses a default value for a parameter).
  • Documentation on Skija methods is very welcome!

These don’t:

  • Things that don’t directly map to anything in Java (pointers, etc).
  • New classes, new methods that don’t exist in Skia.
  • Code that combines Skia APIs together for special use-cases.

Useful things built on top of Skia/Skija are always welcome — as libraries.

skija's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

skija's Issues

skija.dll fails to load (windows 10 pro / directx12 )

Hi.. coming back from an unsolved issue concerning incompatibility with systems that do not support dx12.
#38

Tested on 0.116.1 version.
But surprisingly, trying to test some very simple skija examples in a different machine that supports dx12 (12gen intel, NVidia RTX GPU)) gives fatal error on loading skija.dll. I was wondering if we can spot the issue. I include a log. Tested on 3 different jvms, same results.

Tested on 0.116.1 version.
image

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x00007ff9a3b5cc00, pid=112124, tid=29404
#
# JRE version: OpenJDK Runtime Environment (17.0.7+7) (build 17.0.7+7-LTS)
# Java VM: OpenJDK 64-Bit Server VM (17.0.7+7-LTS, mixed mode, sharing, tiered, compressed oops, compressed class ptrs, g1 gc, windows-amd64)
# Problematic frame:
# V  [jvm.dll+0x1cc00]
#
# No core dump will be written. Minidumps are not enabled by default on client versions of Windows
#
# An error report file with more information is saved as:
# E:\...\hs_err_pid112124.log
#
# If you would like to submit a bug report, please visit:
#   https://bell-sw.com/support
#

hs_err_pid112124.log

sun.misc.Cleaner class not found

Exception in thread "main" java.lang.NoClassDefFoundError: sun.misc.Cleaner

I've been trying to get a HumbleUI app running as a native executable (GraalVM).
When executing the uberjar (with aot), the mentioned error is thrown. (I followed this approach btw.: https://github.com/tonsky/uberdeps)

https://stackoverflow.com/questions/62815536/why-am-i-getting-the-error-noclassdeffounderror-sun-misc-unsafe-when-trying-t
As stated in this post, it is one of the packages removed from Java 9 and can be fixed by adding requires jdk.unsupported; to module-info.java.
I tried to test it locally but I wasn't able to properly integrate the local Skija build into the Clojure project.

Building the uberjar with Java 8 wouldn't work because other packages are compiled with Java 11.

Force no antialiasing when painting / set texture filter quality?

I recently switched from SkiaSharp to Skija and so far, Skija is doing a fantastic job.
But I encountered two dissimilarities, I could not resolve:

  1. How do i "force" no antialiasing when painting. The function description states: paint.setAntiAlias() "requests" antialiasing being enabled or disabled. But in my case, it does not disable it, when I want it to. In SkiaSharp I was able to enable/disable antialiasing from one draw call to another (which was quite uesful in some scenarios) is this not possible in Skija?

  2. In SkiaSharp I had the option to set a "filterQuality" state when painting a texture/image. (Which gave me the option to set nearest neighbour, bilinear, trilienar filtering) but I simply cannot find this feature in Skija. Is texture filtering handled differently in Skija? [Solved!]

How to update shader uniforms?

I have made a runtime effect with RuntimeEffect.makeForShader(shaderSrc);
I have created the shader with effect.makeShader(Data.makeFromBytes(data), null, null, false) (data being a byte buffer of uniforms)
I created the paint with paint = new Paint(); paint.setShader(shader);

The problem is, none of these classes have a method to update the uniforms in the shader.
How do I do this? I can't just re create the shader as it takes ~3ms to create and uniforms need to be updated each frame.

Text Style Scene in examples crashed on linux because of AssertionError

This happens on my x86 and arm devices:

Exception in thread "main" java.lang.AssertionError
        at io.github.humbleui.skija.examples.scenes.TextStyleScene.draw(TextStyleScene.java:110)
        at io.github.humbleui.skija.examples.scenes.Scenes.draw(Scenes.java:103)
        at io.github.humbleui.skija.examples.lwjgl.Window.draw(Main.java:143)
        at io.github.humbleui.skija.examples.lwjgl.Window.loop(Main.java:214)
        at io.github.humbleui.skija.examples.lwjgl.Window.run(Main.java:62)
        at io.github.humbleui.skija.examples.lwjgl.Main.main(Main.java:33)

Some path effects causing shader compilation errors on macOS

I have the code from the original PathEffects demo setup in Kotlin, when I run it with some basic LWJGL code on Windows everything renders fine, but when I run it on macOS it gives me this error each frame:

Shader compilation error

1 #version 110
2
3 uniform vec4 sk_RTAdjust;
4 uniform vec2 uatlas_adjust_S0;
5 attribute vec4 fillBounds;
6 attribute vec4 color;
7 attribute vec4 locations;
8 varying vec2 vatlasCoord_S0;
9 varying vec4 vcolor_S0;
10 void main() {
11 vec2 unitCoord = vec2(float(gl_VertexID & 1), float(gl_VertexID >> 1));
12 vec2 devCoord = mix(fillBounds.xy, fillBounds.zw, unitCoord);
13 vec2 atlasTopLeft = vec2(abs(locations.x) - 1.0, locations.y);
14 vec2 devTopLeft = locations.zw;
15 bool transposed = locations.x < 0.0;
16 vec2 atlasCoord = devCoord - devTopLeft;
17 if (transposed) {
18 atlasCoord = atlasCoord.yx;
19 }
20 atlasCoord += atlasTopLeft;
21 vatlasCoord_S0 = atlasCoord * uatlas_adjust_S0;
22 vcolor_S0 = color;
23 gl_Position = vec4(devCoord, 0.0, 1.0);
24 gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
25 }
26
Errors:
ERROR: 0:11: Use of undeclared identifier 'gl_VertexID'
ERROR: 0:11: Use of undeclared identifier 'gl_VertexID'
ERROR: 0:12: Use of undeclared identifier 'unitCoord'
ERROR: 0:16: Use of undeclared identifier 'devCoord'
ERROR: 0:18: Use of undeclared identifier 'atlasCoord'
ERROR: 0:18: Use of undeclared identifier 'atlasCoord'
ERROR: 0:20: Use of undeclared identifier 'atlasCoord'
ERROR: 0:21: Use of undeclared identifier 'atlasCoord'
ERROR: 0:23: Use of undeclared identifier 'devCoord'

here is my draw code:

var x = 20f
var y = 20f

Path().moveTo(-5f, -3f).lineTo(5f, 0f).lineTo(-5f, 3f).closePath().use { pattern ->
    Path().lineTo(10f, 0f).lineTo(10f, 1f).lineTo(0f, 1f).closePath().use { dash ->
        Paint().setColor(0x20457b9d).setMode(PaintMode.STROKE).setStrokeWidth(1f).use { stroke ->
            Paint().setColor(-0x1890af).setMode(PaintMode.STROKE).setStrokeWidth(1f).use { fill ->
                Path().moveTo(100f, 10f).lineTo(190f, 190f).lineTo(10f, 190f).closePath().use { figure ->
                    val offset = 1f - System.currentTimeMillis() % 1000 / 1000f
                    val effects = arrayOf(
                        PathEffect.makePath1D(pattern, 10f, 10 * offset, PathEffect.Style.TRANSLATE),
                        PathEffect.makePath1D(pattern, 20f, 20 * offset, PathEffect.Style.TRANSLATE),
                        PathEffect.makePath1D(pattern, 20f, 20 * offset, PathEffect.Style.ROTATE),
                        PathEffect.makePath1D(pattern, 20f, 20 * offset, PathEffect.Style.MORPH)
                            .makeCompose(PathEffect.makeCorner(50f)),
                        PathEffect.makePath1D(dash, 15f, 15 * offset, PathEffect.Style.MORPH),
                        PathEffect.makePath2D(Matrix33.makeScale(15f), pattern),
                        PathEffect.makeLine2D(1f, Matrix33.makeScale(3f, 3f)),
                        PathEffect.makeLine2D(1f, Matrix33.makeScale(3f, 3f).makeConcat(Matrix33.makeRotate(30f))),
                        PathEffect.makeCorner(10f),
                        PathEffect.makeCorner(30f),
                        PathEffect.makeDash(floatArrayOf(10f, 10f), 20 * offset),
                        PathEffect.makeDash(floatArrayOf(10f, 5f), 15 * offset),
                        PathEffect.makeDash(floatArrayOf(10f, 5f, 2f, 5f), 22 * offset),
                        PathEffect.makeDiscrete(5f, 2f, (System.currentTimeMillis() / 32).toInt()),
                        PathEffect.makeDash(floatArrayOf(10f, 5f, 2f, 5f), 22 * offset)
                            .makeCompose(PathEffect.makeCorner(50f)),
                        PathEffect.makeDash(floatArrayOf(10f, 5f, 2f, 5f), 22 * offset)
                            .makeSum(PathEffect.makeCorner(50f)),
                    )
                    for (effect in effects) {
                        if (x > 0f && x + 200f > width) {
                            x = 0f
                            y += 200f
                        }
                        canvas!!.save()
                        canvas!!.translate(x.toFloat(), y.toFloat())
                        canvas!!.drawPath(figure, stroke)
                        fill.setPathEffect(effect)
                        canvas!!.drawPath(figure, fill)
                        fill.setPathEffect(null)
                        effect.close()
                        canvas!!.restore()
                        x += 200
                    }
                }
            }
        }
    }
}

When no drawing (or only basic drawing) Is done before the path effects are drawn, only the first five fail to be drawn. However if I place this code after the rest of my scene is drawn, all effects fail.

This happens on the latest version (0.109.2)

Typeface._nMakeFromName returns null on linux

The fourteenth scene (Font Size) in the examples crashes. Here is the log:

glavo@W510 ~/P/Skija (linux-arm64) [1]> python3 ./examples/kwinit/script/run.py
Using Skia from /home/glavo/Projects/Skija/platform/Skia-m109-664500fa93-linux-Release-arm64
-- Configuring done
-- Generating done
-- Build files have been written to: /home/glavo/Projects/Skija/platform/target/linux-arm64/native
ninja: no work to do.
    Finished release [optimized + debuginfo] target(s) in 0.11s
2023-02-28T15:00:26.590149605Z [DEBUG] Loading /home/glavo/Projects/Skija/platform/target/linux-arm64/classes/io/github/humbleui/skija/linux/arm64/libskija.so
WARNING in native method: JNI call made with exception pending
        at noria.kwinit.impl.ExternalAPI.runEventLoop(Native Method)
        at noria.kwinit.impl.Main.run(Main.java:65)
        at noria.kwinit.impl.Main.main(Main.java:18)
Exception in thread "main" java.lang.RuntimeException: Can't wrap nullptr
        at io.github.humbleui.skija.impl.Native.<init>(Native.java:12)
        at io.github.humbleui.skija.impl.Managed.<init>(Managed.java:14)
        at io.github.humbleui.skija.impl.Managed.<init>(Managed.java:10)
        at io.github.humbleui.skija.impl.RefCnt.<init>(RefCnt.java:7)
        at io.github.humbleui.skija.Typeface.<init>(Typeface.java:393)
        at io.github.humbleui.skija.Typeface.makeFromName(Typeface.java:123)
        at io.github.humbleui.skija.examples.scenes.FontSizeScene.lambda$new$0(FontSizeScene.java:55)
        at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
        at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:992)
        at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
        at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
        at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:575)
        at java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:260)
        at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:616)
        at io.github.humbleui.skija.examples.scenes.FontSizeScene.<init>(FontSizeScene.java:59)
        at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
        at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
        at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
        at java.base/java.lang.reflect.ReflectAccess.newInstance(ReflectAccess.java:128)
        at java.base/jdk.internal.reflect.ReflectionFactory.newInstance(ReflectionFactory.java:347)
        at java.base/java.lang.Class.newInstance(Class.java:645)
        at io.github.humbleui.skija.examples.scenes.Scenes.newScene(Scenes.java:73)
        at io.github.humbleui.skija.examples.scenes.Scenes.setScene(Scenes.java:89)
        at io.github.humbleui.skija.examples.scenes.Scenes.prevScene(Scenes.java:81)
        at noria.kwinit.impl.Main.onKeyDown(Main.java:172)
        at noria.kwinit.impl.Main.lambda$run$1(Main.java:84)
        at noria.kwinit.impl.ExternalAPI.runEventLoop(Native Method)
        at noria.kwinit.impl.Main.run(Main.java:65)
        at noria.kwinit.impl.Main.main(Main.java:18)
Traceback (most recent call last):
  File "/home/glavo/Projects/Skija/./examples/kwinit/script/run.py", line 59, in <module>
    sys.exit(main())
  File "/home/glavo/Projects/Skija/./examples/kwinit/script/run.py", line 44, in main
    subprocess.check_call([
  File "/usr/lib/python3.10/subprocess.py", line 369, in check_call
    raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command '['java', '--class-path', 'target/classes:/home/glavo/.m2/repository/org/jetbrains/annotations/20.1.0/annotations-20.1.0.jar:/home/glavo/.m2/repository/org/projectlombok/lombok/1.18.22/lombok-1.18.22.jar:/home/glavo/.m2/repository/io/github/humbleui/types/0.2.0/types-0.2.0.jar:/home/glavo/.m2/repository/com/google/code/gson/gson/2.8.6/gson-2.8.6.jar:../../platform/target/linux-arm64/classes:../../shared/target/classes-java9:../../shared/target/classes', '-Djava.awt.headless=true', '-enableassertions', '-enablesystemassertions', '-Xcheck:jni', '-Dskija.logLevel=DEBUG', 'noria.kwinit.impl.Main']' returned non-zero exit status 1.

[Feature request] Build a windows release without Direct3D dependency

I've been struggling to build locally a Windows release without Direct3D 12, and try an only OpenGL dll version. The build system is a bit confusing with all those python scripts, but GitHub actions is ready for this, I guess.
If it is relatively easy please update the action scripts in order to support a special "only OpenGL" release without the Direct3D 12 dependency.
This might expand the use of Skija to GPUs that do not support DirectX 12 (like intel 4th gen).
If it will never happen for any reason, please close this "issue"!

reference: #15 (comment)

Exception in thread "main" java.lang.UnsatisfiedLinkError: Can't load ... skija.dll

Exception in thread "main" java.lang.UnsatisfiedLinkError: Can't load C:\Users\...\AppData\Local\Temp\skija_0.100.0\skija.dll
	at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:1793)
	at java.base/java.lang.System.load(System.java:672)
	at io.github.humbleui.skija.impl.Library.load(Library.java:73)
	at io.github.humbleui.skija.impl.Library.staticLoad(Library.java:47)
	at io.github.humbleui.skija.Surface.<clinit>(Surface.java:10)
	...

same error when using System.load(), System.loadLibrary(), System.setProperty() etc.
windows, netbeans, pre-compiled skija 0.100, skija.dll icudtl.dat files EXTRACTED OK in temp dir
any ideas on how to get it loaded?

Seems like a great project! thanks

Allow usage of maven or gradle to build shared java code.

Allowing the usage of Maven or Gradle to build the shared code for Skija will improve how easy it is for new people to contribute to the project easier because getting the project to open in a popular Java IDE and just work is not possible without pre-processing the repo due to file structure not being standard and possibly other reasons.

You could look at netty.io for inspiration if you want to extend the building to building cross-platform native libraries with Maven/Gradle but such tasks are usually inferior compared to the alternatives.

How do we add Metal Kit Device to the surface and render

Apple has the following cpp headers available

#include <Metal/Metal.hpp>
#include <AppKit/AppKit.hpp>
#include <MetalKit/MetalKit.hpp>

is how we access metal kit device in cpp but how do we pass this the the long in skija
MTL::Device* _pDevice;

How to draw mask image in skija?

I want to draw the two pictures together to realize the function of picture mask, just like css style mask-image ,
So I want to know if any ways to use mask-image in skaji?

Looking forward to your reply
thanks

Backport to Java 8

There seems to only be only one Java 9+ feature used by this project currently and usage of Java 8 for legacy projects is still significant while they slowly transition to Java 11+.

Here is an example licensed under the MIT license for abstracting the cleaner API for both Java 8 and Java 9+ support: https://github.com/aikar/cleaner

FR: Native wayland support?

Right now skija on my laptop is running through XWayland, and it's not good enough because of scaling issues. Is there a way that I can run the UI in native wayland

I'm using https://github.com/lilactown/humble-starter with deps.edn|io.github.humbleui/humbleui|git/sha set to 69faab0123f61c242e34061e8bcaaf772b46d0e1(lastest commit for humbeui for now).

Doesn't seem to be any way to use LCD font rendering?

NOTE: originally posted to the old (jetbrains) repository by accident: here. @tonsky replied with example which suggested that FontEdging achieves this. It does not! More below.

I couldn't find anything which looked like it would do this in the source.

I note that in SkiaSharp this is achieved via SKPaint.LcdRenderText property.

Have I missed something? Could this be added?

Is there any workaround/hack to access this?

It seems to me that getting font rendering to look exactly like it does in Chrome and other Skia based apps would be one of the biggest reasons for trying/using skija, so this seems pretty major to me.

Response in the old forum linked to an example which uses FontEdging.SUBPIXEL_ANTI_ALIAS presumably intended to force LCD rendering. (I couldn't actually figure out how to run it, but my example using FontEdging is below:)


import io.github.humbleui.skija.*;

import javax.imageio.*;
import javax.swing.*;
import java.io.*;

public class TextAliasing {
    public static void main(String[] ignored) throws Exception {
        int width = 130;
        int height = 30;
        Surface surface = Surface.makeRasterN32Premul(width, height);
        Canvas canvas = surface.getCanvas();
        canvas.clear(0xFFFFFFFF);

        Typeface typeface = Typeface.makeFromName("Arial", FontStyle.NORMAL);
        Font font = new Font(typeface, 24);
        font.setEdging(FontEdging.SUBPIXEL_ANTI_ALIAS);
        Paint paint = new Paint();
        paint.setColor(0xFF000000);
        canvas.drawString("Hello World", 3, height * 0.8f, font, paint);

        byte[] pngBytes = surface.makeImageSnapshot().encodeToData().getBytes();
        java.awt.Image image = ImageIO.read(new ByteArrayInputStream(pngBytes));
        image = image.getScaledInstance(width * 4, height * 4, java.awt.Image.SCALE_REPLICATE);
        JFrame frame=new JFrame();
        frame.getContentPane().add(new JLabel(new ImageIcon(image)));
        //frame.getContentPane().add(component);
        frame.pack();
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
        //java.nio.file.Files.write(java.nio.file.Path.of("output.png"), pngBytes);

    }
}

The results are as follows, using ANTI_ALIAS top, SUBPIXEL_ANTI_ALIAS bottom:

image

As you can see, so-called SUBPIXEL_ANTI_ALIAS does not perform subpixel rendering. Every pixel in the image is grey. I'm not sure what it is actually doing, but it isn't subpixel rendering which uses distinct values for red, green and blue.

The SkiaSharp LcdRenderText property can be seen in action about one third way down this page and it enables real LCD subpixel rendering (there is some blown-up text demonstrating this).

Many thanks.

LWJGL, JRE Exception: Access Violation @ DirectContext.makeGL

I tried recreating the skija + lwjgl example, but I can't get it running:
Everytime I start the project the JRE Crashes, showing an EXCEPTION_ACCESS_VIOLATION error.
If I step through the program it crashes at the DirectContext.makeGL() call.

Before that, I was able to create a GL window and GL context. Clearing the window also worked fine.
So I assume the openGL part of the project is working fine.

Maybe I made an obvious error, when I added the libraries to my project.
(I did not follow the gradle/maven etc. guide. I added them manually)
I'm pretty new to programming in Java, so it's likely I made a very stupid and obvious error.
Maybe somebody else can help me figure out, what might be the issue here.

Project Setup:
Platform: Windows 10 x64
JRE: Azul Zulu 17
IDE: IntelliJ
LWJGL: 3.3.1 (Minimal openGL)
Skija: 0.109.1 (Release version)

Code:
(My code is basicly identical to the Skija, lwjgl example)

import java.nio.IntBuffer;
import java.util.*;

import org.lwjgl.glfw.*;
import org.lwjgl.opengl.*;
import org.lwjgl.system.MemoryStack;

import static org.lwjgl.glfw.Callbacks.*;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.system.MemoryUtil.*;

import io.github.humbleui.skija.*;
import io.github.humbleui.skija.impl.*;

class Window
{
    //GL
    public long window;
    public int width;
    public int height;
    public float dpi = 1f;
    public int xpos = 0;
    public int ypos = 0;
    public boolean vsync = true;
    public boolean stats = true;
    private int[] refreshRates;
    private String os = System.getProperty("os.name").toLowerCase();

    //SKIA
    private DirectContext context;
    private BackendRenderTarget renderTarget;
    private Surface surface;
    private Canvas canvas;

    //-----------------------------------------------------------------------------------------------------//
    //Create Window
    //-----------------------------------------------------------------------------------------------------//
    private void createWindow(int x, int y, int width, int height)
    {
        glfwDefaultWindowHints(); // optional, the current window hints are already the default
        glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // the window will stay hidden after creation
        glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); // the window will be resizable

        window = glfwCreateWindow(width, height, "Skija LWJGL Demo", 0, 0);
        if (window == 0)
        {
            throw new RuntimeException("Failed to create the GLFW window");
        }

        glfwSetKeyCallback(window, (window, key, scancode, action, mods) ->
        {
            if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE)
            {
                glfwSetWindowShouldClose(window, true);
            }
        });

        glfwSetWindowPos(window, x, y);
        updateDimensions();
        xpos = width / 2;
        ypos = height / 2;

        glfwMakeContextCurrent(window);
        glfwSwapInterval(vsync ? 1 : 0); // Enable v-sync
        glfwShowWindow(window);
    }

    //-----------------------------------------------------------------------------------------------------//
    //Get Refresh Rates
    //-----------------------------------------------------------------------------------------------------//
    private int[] getRefreshRates()
    {
        var monitors = glfwGetMonitors();
        int[] res = new int[monitors.capacity()];
        for (int i=0; i < monitors.capacity(); ++i)
        {
            res[i] = glfwGetVideoMode(monitors.get(i)).refreshRate();
        }
        return res;
    }

    //-----------------------------------------------------------------------------------------------------//
    //Run
    //-----------------------------------------------------------------------------------------------------//
    public void run(int x, int y, int width, int height)
    {
        refreshRates = getRefreshRates();

        createWindow(x, y, width, height);
        loop();

        glfwFreeCallbacks(window);
        glfwDestroyWindow(window);
        glfwTerminate();
        glfwSetErrorCallback(null).free();
    }

    //-----------------------------------------------------------------------------------------------------//
    //Update Dimensions
    //-----------------------------------------------------------------------------------------------------//
    private void updateDimensions()
    {
        int[] width = new int[1];
        int[] height = new int[1];
        glfwGetFramebufferSize(window, width, height);

        float[] xscale = new float[1];
        float[] yscale = new float[1];
        glfwGetWindowContentScale(window, xscale, yscale);
        assert xscale[0] == yscale[0] : "Horizontal dpi=" + xscale[0] + ", vertical dpi=" + yscale[0];

        this.width = (int) (width[0] / xscale[0]);
        this.height = (int) (height[0] / yscale[0]);
        this.dpi = xscale[0];
        System.out.println("FramebufferSize " + width[0] + "x" + height[0] + ", scale " + this.dpi + ", window " + this.width + "x" + this.height);
    }

    //-----------------------------------------------------------------------------------------------------//
    //Initialize Skia
    //-----------------------------------------------------------------------------------------------------//
    private void initSkia()
    {
        Stats.enabled = true;

        if (surface != null)
        {
            surface.close();
        }
        if (renderTarget != null)
        {
            renderTarget.close();
        }

        renderTarget = BackendRenderTarget.makeGL(
                (int) (width * dpi),
                (int) (height * dpi),
                /*samples*/16,
                /*stencil*/8,
                /*fbId*/0,
                FramebufferFormat.GR_GL_RGBA8);

        surface = Surface.makeFromBackendRenderTarget(
                context,
                renderTarget,
                SurfaceOrigin.BOTTOM_LEFT,
                SurfaceColorFormat.RGBA_8888,
                ColorSpace.getDisplayP3(),  // TODO load monitor profile
                new SurfaceProps(PixelGeometry.BGR_H));

        canvas = surface.getCanvas();
    }

    //-----------------------------------------------------------------------------------------------------//
    //Draw
    //-----------------------------------------------------------------------------------------------------//
    private void draw()
    {
        //var canvas = surface.getCanvas();
        //canvas.clear(0);
        //Paint paint = new Paint();
        //paint.setColor4f(new Color4f(1f, 1f, 1f, 1f), ColorSpace.getSRGB());
        //paint.setAntiAlias(true);
        //canvas.drawCircle(640f, 360f, 150f, paint);
        context.flush();
        glfwSwapBuffers(window);
    }

    //-----------------------------------------------------------------------------------------------------//
    //Loop
    //-----------------------------------------------------------------------------------------------------//
    private void loop()
    {
        GL.createCapabilities();
        if ("false".equals(System.getProperty("skija.staticLoad")))
        {
            Library.load();
        }
        context = DirectContext.makeGL();

        GLFW.glfwSetWindowSizeCallback(window, (window, width, height) ->
        {
            updateDimensions();
            initSkia();
            draw();
        });

        glfwSetCursorPosCallback(window, (window, xpos, ypos) ->
        {
            if(os.contains("mac") || os.contains("darwin"))
            {
                this.xpos = (int) xpos;
                this.ypos = (int) ypos;
            }
            else
            {
                this.xpos = (int) (xpos / dpi);
                this.ypos = (int) (ypos / dpi);
            }
        });

        glfwSetMouseButtonCallback(window, (window, button, action, mods) ->
        {
            System.out.println("Button " + button + " " + (action == 0 ? "released" : "pressed"));
        });

        glfwSetScrollCallback(window, (window, xoffset, yoffset) ->
        {
            System.out.println("Scroll " + xoffset + "/" + yoffset);
        });

        glfwSetKeyCallback(window, (window, key, scancode, action, mods) ->
        {
            if (action == GLFW_PRESS)
            {
                switch (key)
                {
                    case GLFW_KEY_V:
                        vsync = !vsync;
                        glfwSwapInterval(vsync ? 1 : 0);
                        System.out.println("Vsync " + (vsync ? "ON" : "OFF"));
                        break;
                    case GLFW_KEY_G:
                        System.out.println("Before GC " + Stats.allocated);
                        System.gc();
                        break;
                }
            }
        });

        initSkia();

        while (!glfwWindowShouldClose(window))
        {
            draw();
            glfwPollEvents();
        }
    }
}

public class SkiaMain
{
    public static void main(String [] args) throws Exception
    {
        GLFWErrorCallback.createPrint(System.err).set();
        if (!glfwInit())
        {
            throw new IllegalStateException("Unable to initialize GLFW");
        }

        GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
        int width = 1280;
        int height = 720;
        new Window().run(Math.max(0, (vidmode.width() - width) / 2), Math.max(0, (vidmode.height() - height) / 2), width, height);
    }
}

Error log:
Maybe somebody else can get some usefule information from this:
error.log

Memory Leak: Runtime Effect + Runtime generated Images

I'm currently trying to implement a runtime effect which uses runtime generated images as shader inputs.
I tried to replicate the RuntimeEffectScene example:
https://github.com/HumbleUI/Skija/blob/master/examples/scenes/src/RuntimeEffectScene.java

But instead of a static texture image from a file, I wanted to use runtime created images drawn to offscreen surfaces.
My implementation "works" (The output looks like what I expected), but it also introduced a memory leak issue.
I tried to track down the leaking object, but so far with no avail. Maybe my approach is simply wrong...
If somebody could help me figure this out, that would be awesome.

Some minimalist code to showcase my current implementation:

public class Main
{
    public static void main(String [] args) throws Exception
    {
        GLFWErrorCallback.createPrint(System.out).set();
        if (!GLFW.glfwInit())
        {
            throw new IllegalStateException("Error: Unable to initialize GLFW");
        }
        loop();
    }

    private static void loop()
    {
        //Create window (Setup GLFW and SKIJA)
        GameWindow window = new GameWindow();

        //Create main offscreen target
        OffscreenTarget mainTarget = new OffscreenTarget(window.getContext(), 640, 480, 0);

        //Create debug effect
        RuntimeEffect effect = RuntimeEffect.makeForShader(
                "uniform shader in_texture0;\n" +
                "uniform shader in_mask0;\n" +
                "\n" +
                "float4 main(float2 p)\n" +
                "{\n" +
                "    float4 texel = in_texture0.eval(p);\n" +
                "    float4 texelMask = in_mask0.eval(p);\n" +
                "    float4 result = float4(texel.r * texelMask.r, texel.g * texelMask.r, texel.b * texelMask.r, texel.a * texelMask.r);\n" +
                "\treturn result;\n" +
                "}"
        );

        while (window.isAlive())
        {
            GLFW.glfwMakeContextCurrent(window.getWindowHandle());
            GLFW.glfwPollEvents();

            //Create debug offscreen targets
            OffscreenTarget debugTarget1 = new OffscreenTarget(window.getContext(), 400, 400, 0);
            OffscreenTarget debugTarget2 = new OffscreenTarget(window.getContext(), 400, 400, 0);

            //Draw gradient to debug target 1
            debugTarget1.clear(Color.makeARGB(0, 0, 0, 0));
            Shader gradientShader = Shader.makeLinearGradient(new Point(0, 0), new Point(400, 400), new int[] { 0xFF247ba0, 0xFFf3ffbd });
            Paint pt1 = new Paint();
            pt1.setShader(gradientShader);
            debugTarget1.getSurface().getCanvas().drawRect(new Rect(0, 0, 400, 400), pt1);
            gradientShader.close();

            //Draw black and white mask to debug target 2
            debugTarget2.clear(Color.makeARGB(255, 0, 0, 0));
            Paint pt2 = new Paint();
            pt2.setAntiAlias(true);
            pt2.setColor(Color.makeARGB(255, 255, 255, 255));
            debugTarget2.getSurface().getCanvas().drawOval(new Rect(0, 0, 400, 400), pt2);

            //Create runtime shader from debug offscreen target 1 + 2 images
            Shader[] debugShaderChildShaders = new Shader[2];
            Image debugImage1 = debugTarget1.getSurface().makeImageSnapshot();
            debugShaderChildShaders[0] = debugImage1.makeShader();
            Image debugImage2 = debugTarget2.getSurface().makeImageSnapshot();
            debugShaderChildShaders[1] = debugImage2.makeShader();

            //Create runtime shader of debug effect
            Shader runtimeShader = effect.makeShader(null, debugShaderChildShaders, null);

            //Clear main offscreen target
            mainTarget.clear(Color.makeARGB(255, 32, 32, 32));

            //Draw runtime debug shader to main target
            mainTarget.getSurface().getCanvas().save();
            Paint pt3 = new Paint();
            pt3.setShader(runtimeShader);
            mainTarget.getSurface().getCanvas().translate(120, 40);
            mainTarget.getSurface().getCanvas().drawRect(new Rect(0, 0, 400, 400), pt3);
            mainTarget.getSurface().getCanvas().restore();

            //Try to close all runtime objects -->
            runtimeShader.close();
            debugShaderChildShaders[0].close();
            debugShaderChildShaders[1].close();
            debugImage1.close();
            debugImage2.close();
            debugTarget1.dispose();
            debugTarget2.dispose();
            //...Still loosing about 200mb of ram + vram per second

            //Draw main offscreen target to window
            window.draw(mainTarget);

            window.getContext().flush();
            GLFW.glfwSwapBuffers(window.getWindowHandle());
        }

        effect.close();
        mainTarget.dispose();
        window.closeWindow();
    }
}

The OffscreenTarget.dispose() method just calls Surface.close() on the encapsulated Skija Surface.

Canvas not marked as closed when its parent Surface is collected

While both Canvas and Surface implement AutoCloseable, when the GC hits, the Canvas is freed by its parent Surface (in the native lib) without marking the Canvas as closed. This causes a crash when doing anything with the orphaned Canvas object. It's most noticeable when resizing the window, since the whole thing gets thrown away (surface.close(); renderTarget.close()... unless I'm doing it wrong lol).

I believe when calling getCanvas() in Surface, the resulting Java object should be stored (cached) in the Surface object and returned with every subsequent call, and of course explicitly closed on cleanup.

public Canvas getCanvas() {
try {
Stats.onNativeCall();
long ptr = _nGetCanvas(_ptr);
return ptr == 0 ? null : new Canvas(ptr, false, this);
} finally {
ReferenceUtil.reachabilityFence(this);
}
}

and possibly the opposite in Canvas where it should return its parent Surface (same as _owner).

public Surface getSurface() {
try {
Stats.onNativeCall();
long ptr = _nGetSurface(_ptr);
return ptr == 0 ? null : new Surface(ptr);
} finally {
ReferenceUtil.reachabilityFence(this);
}
}

Native Code Error when trying to use RadialGradientShader

Hi there, I'm trying to make use of Skija with Clojure, as a way to learn both. Maybe also SKSL shaders as well (although I can't figure out how to make a shader from SKSL yet).
Anyways, Java crashed when running this Clojure code:

(ns shaders.core
  (:require
    [clojure.main :as cl-main]
    [nrepl.server :as nrepl])
  (:import
    (io.github.humbleui.skija BackendRenderTarget Canvas ColorSpace
                              DirectContext FramebufferFormat Surface
                              SurfaceColorFormat SurfaceOrigin Paint Shader)
    (org.lwjgl.glfw Callbacks GLFW GLFWErrorCallback)
    (org.lwjgl.opengl GL GL11)
    (org.lwjgl.system MemoryUtil)))

(set! *warn-on-reflection* true)

(defn color [^long l]
  (.intValue (Long/valueOf l)))

(def *rect-color (atom (color 0xFFCC3333)))

(defn draw [^Canvas canvas]
  (let [paint (doto (Paint.) (.setShader (Shader/makeRadialGradient
                                           320.0
                                           240.0
                                           50.0
                                           [0xffffff 0x923423])))]
    (.drawPaint paint)))

(defn display-scale [window]
  (let [x (make-array Float/TYPE 1)
        y (make-array Float/TYPE 1)]
    (GLFW/glfwGetWindowContentScale window x y)
    [(first x) (first y)]))

(defn -main [& args]
  (.set (GLFWErrorCallback/createPrint System/err))
  (GLFW/glfwInit)
  (GLFW/glfwWindowHint GLFW/GLFW_VISIBLE GLFW/GLFW_FALSE)
  (GLFW/glfwWindowHint GLFW/GLFW_RESIZABLE GLFW/GLFW_TRUE)
  (let [width 640
        height 480
        window (GLFW/glfwCreateWindow width height "Messing with Skija" MemoryUtil/NULL MemoryUtil/NULL)]
    (GLFW/glfwMakeContextCurrent window)
    (GLFW/glfwSwapInterval 1)
    (GLFW/glfwShowWindow window)
    (GL/createCapabilities)

    (doto (Thread. #(cl-main/main))
      (.start))

    (nrepl/start-server :port 7888)
    (println "nREPL server started at locahost:7888")

    (let [context (DirectContext/makeGL)
          fb-id   (GL11/glGetInteger 0x8CA6)
          [scale-x scale-y] (display-scale window)
          target  (BackendRenderTarget/makeGL (* scale-x width) (* scale-y height) 0 8 fb-id FramebufferFormat/GR_GL_RGBA8)
          surface (Surface/makeFromBackendRenderTarget context target SurfaceOrigin/BOTTOM_LEFT SurfaceColorFormat/RGBA_8888 (ColorSpace/getSRGB))
          canvas  (.getCanvas surface)]
      (.scale canvas scale-x scale-y)
      (loop []
        (when (not (GLFW/glfwWindowShouldClose window))
          (.clear canvas (color 0xFFFFFFFF))
          (let [layer (.save canvas)]
            (draw canvas)
            (.restoreToCount canvas layer))
          (.flush context)
          (GLFW/glfwSwapBuffers window)
          (GLFW/glfwPollEvents)
          (recur)))

      (Callbacks/glfwFreeCallbacks window)
      (GLFW/glfwHideWindow window)
      (GLFW/glfwDestroyWindow window)
      (GLFW/glfwPollEvents)

      (.close surface)
      (.close target)
      (.close context)

      (GLFW/glfwTerminate)
      (.free (GLFW/glfwSetErrorCallback nil))
      (nrepl/stop-server 7888)
      (shutdown-agents)
      )))

(comment
  (reset! shaders.core/*rect-color (shaders.core/color 0xFF33CC33)))

This was the terminal error message:

lein run           
Reflection warning, shaders/core.clj:21:42 - call to static method makeRadialGradient on io.github.humbleui.skija.Shader can't be resolved (argument types: double, double, double, clojure.lang.APersistentVector).
Reflection warning, shaders/core.clj:26:5 - reference to field drawPaint on io.github.humbleui.skija.Paint can't be resolved.
Reflection warning, shaders/core.clj:31:5 - call to static method glfwGetWindowContentScale on org.lwjgl.glfw.GLFW can't be resolved (argument types: unknown, unknown, unknown).
Clojure 1.11.1
nREPL server started at locahost:7888
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "clojure.lang.IFn.invoke(Object)" because the return value of "clojure.lang.Var.get()" is null
        at clojure.pprint$write_out.invokeStatic(pprint_base.clj:194)
        at clojure.pprint$pprint$fn__10392.invoke(pprint_base.clj:249)
        at clojure.pprint$pprint.invokeStatic(pprint_base.clj:248)
        at clojure.pprint$pprint.invoke(pprint_base.clj:241)
        at clojure.pprint$pprint.invokeStatic(pprint_base.clj:245)
        at clojure.pprint$pprint.invoke(pprint_base.clj:241)
        at clojure.lang.Var.invoke(Var.java:384)
        at clojure.main$report_error$fn__9280$fn__9281.invoke(main.clj:603)
        at clojure.main$report_error$fn__9280.invoke(main.clj:602)
        at clojure.main$report_error.invokeStatic(main.clj:601)
        at clojure.main$main.invokeStatic(main.clj:666)
        at clojure.main$main.doInvoke(main.clj:616)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at clojure.lang.Var.applyTo(Var.java:705)
        at clojure.main.main(main.java:40)
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x00007ffa222a1770, pid=30010, tid=51459
#
# JRE version: OpenJDK Runtime Environment Homebrew (18.0.2) (build 18.0.2+0)
# Java VM: OpenJDK 64-Bit Server VM Homebrew (18.0.2+0, mixed mode, sharing, tiered, compressed oops, compressed class ptrs, g1 gc, bsd-amd64)
# Problematic frame:
# C  [libGL.dylib+0x5770]  glBindFramebuffer+0x11
#

And this was the MacOS error report..

Hopefully someone can be of some help. Maybe there's something painfully obvious I'm missing.

[Question] Multiple windows with a shared DirectContext?

I'm currently trying to figure out how to draw to multiple windows, but with no avail.
All I could achieve so far is one window (the primary one) to render correctly, while the secondary window just displays a black screen.
I tried replicating the LWJGL example of how to run multiple GL windows simultaneously, but that wasn't really helpful:
https://github.com/LWJGL/lwjgl3/blob/master/modules/samples/src/test/java/org/lwjgl/demo/glfw/MultipleWindows.java
Mainly because the LWJGL example relies on multiple context objects (one for each window), which I would like to avoid (if possible).

Some boilerplate code of my general order of operation so far:

Window constructor:

//Set default window hints
GLFW.glfwDefaultWindowHints();

//Set window as hidden
GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE);

//Set window resizable flag
if (resizable)
{
    GLFW.glfwWindowHint(GLFW.GLFW_RESIZABLE, GLFW.GLFW_TRUE);
}
else
{
    GLFW.glfwWindowHint(GLFW.GLFW_RESIZABLE, GLFW.GLFW_FALSE);
}

//Create GL window
boolean primaryWindow = false;
if (AEGameWindowManager.getPrimaryWindow() == null)
{
    //--> Create primary window and GL context
    windowHandle = GLFW.glfwCreateWindow(width, height, title, 0, 0);
    if (windowHandle == 0)
    {
        throw new RuntimeException("Error: Failed to create GL window");
    }
    primaryWindow = true;
}
else
{
    //--> Create secondary window, use shared context of primary window
    windowHandle = GLFW.glfwCreateWindow(width, height, title, 0, AEGameWindowManager.getPrimaryWindow().getWindowHandle());
    if (windowHandle == 0)
    {
        throw new RuntimeException("Error: Failed to create GL window");
    }
}

Is my assumption correct, that I have to specify the primary windows' handle as "shared" when I call glfwCreateWindow() on the secondary window creation? (If I want all my windows to use the same context as the primary window)

Window constructor continuation:

//Center window on primary screen
GLFWVidMode vidmode = GLFW.glfwGetVideoMode(GLFW.glfwGetPrimaryMonitor());
int xPos = Math.max(0, (vidmode.width() - width) / 2);
int yPos = Math.max(0, (vidmode.height() - height) / 2);
GLFW.glfwSetWindowPos(windowHandle, xPos, yPos);

//Update window dimensions
updateDimensions();

//Make window context current
GLFW.glfwMakeContextCurrent(windowHandle);

//Set v-sync state
setVSyncState(vsync);

//Unhide window
GLFW.glfwShowWindow(windowHandle);

//Initialize GL context
if (primaryWindow)
{
    initializeGLContext();
}

//Initialize GL window event callbacks
initializeGLCallbacks();

//Update SKIA resources
updateSkia();

Some additional window functions:

//-----------------------------------------------------------------------------------------------------//
//Initialize GL (I only have to do this once, after primary window creation?)
//-----------------------------------------------------------------------------------------------------//
private void initializeGLContext()
{
    //I have no idea what this does...
    if ("false".equals(System.getProperty("skija.staticLoad")))
    {
        Library.load();
    }
    boolean loaded = Library._loaded;

    //Create global gl context
    context = DirectContext.makeGL();
}
//-----------------------------------------------------------------------------------------------------//
//Update SKIA resources
//-----------------------------------------------------------------------------------------------------//
private void updateSkia()
{
    if (surface != null)
    {
        surface.close();
    }
    if (renderTarget != null)
    {
        renderTarget.close();
    }

    renderTarget = BackendRenderTarget.makeGL(
            frameBufferResolution.getX(),
            frameBufferResolution.getY(),
            /*msaaSamples*/0,
            /*stencil*/8,
            /*fbId*/0,
            FramebufferFormat.GR_GL_RGBA8);

    surface = Surface.makeFromBackendRenderTarget(
            context,
            renderTarget,
            SurfaceOrigin.BOTTOM_LEFT,
            SurfaceColorFormat.RGBA_8888,
            ColorSpace.getSRGB(),
            new SurfaceProps(PixelGeometry.RGB_H));
}

Do I have to assign an incremental framebuffer id for each backend render target?
(I tried assigning a different id to the secondary window, but that didn't change anything.)

Window draw():

//-----------------------------------------------------------------------------------------------------//
//Draw this windows' content
//-----------------------------------------------------------------------------------------------------//
public void _draw()
{
    //Assign window to context?
    GLFW.glfwMakeContextCurrent(windowHandle);

    //Clear window surface
    //(The secondary window stays black, even if I use a different clear color...)
    surface.getCanvas().clear(0x00000000);
    
    //...Draw stuff...//

    //Swap front and back buffer
    GLFW.glfwSwapBuffers(windowHandle);
}

Main loop:

//Process window events
GLFW.glfwPollEvents();

AEGameWindow[] windows = AEGameWindowManager.getWindows();
for (AEGameWindow window : windows)
{
    //Draw window content
    window._draw();
}

//(Context is a static field)
AEGameWindow.getContext().flush();

It seems like I have to call DirectContext.flush() once each frame. But when exactly do I have to do this?
After each window was drawn, or after all windows have been drawn?
If I do this after each window was drawn, all windows stop working and only display a black screen...

My main question is:
Are multiple windows even supported by Skija right now?
And if yes, Is there an order of operation, I have to comply to?
And does it matter if I have one context or multiple ones?
The only time I need to access the windows' context is when I create a new Skija Surface.
(I assume because the underlying rendertarget resource is held by the context)
But if that is the case, why can I create a new Skija Image without referencing a context?
(It's just a texture object I assume. It should be assigned to a context?)

Create an image from backend texture handle

I think I'd be cool if you could create an Image from a texture already on the GPU. For example I'm using opengl and do some native rendering to a frame buffer and would like to pass the frame buffers texture id to skia to render it on a canvas and do some effects with it, or even better yet just create an Image from a framebuffer id.

Java 8 doesn't work

asdf.mp4

Windows 11 64 bit
Temurin jdk 8 (for both gradle and project settings)

This is probably a really dumb issue, and I'm probably missing something really important. I'm really sorry if that's the case.

I saw somewhere that Java 8 is supported, but I get this error. In the video it seems like I'm using coretto 8, though I tried it again with Temurin.

The project is just a default intellij java + gradle (kotlin dsl) project, I just added this line in the main function: Paint paint = new Paint()

this is the build.gradle.kts file:
asdf2

Starting two Skija-using applications at the same time may segfault

possibly some race condition on writing/reading /tmp/skija_0.109.1_x64/libskija.so?

Linux Mint 21.1

Current thread (0x00007fbc9402d070):  JavaThread "main" [_thread_in_native, id=10025, stack(0x00007fbc9afeb000,0x00007fbc9b0eb000)]

Stack: [0x00007fbc9afeb000,0x00007fbc9b0eb000],  sp=0x00007fbc9b0e72c8,  free space=1008k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
C  [ld-linux-x86-64.so.2+0x2b1fa]

Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
j  jdk.internal.loader.NativeLibraries.load(Ljdk/internal/loader/NativeLibraries$NativeLibraryImpl;Ljava/lang/String;ZZ)Z+0 [email protected]
j  jdk.internal.loader.NativeLibraries$NativeLibraryImpl.open()Z+56 [email protected]
j  jdk.internal.loader.NativeLibraries.loadLibrary(Ljava/lang/Class;Ljava/lang/String;Z)Ljdk/internal/loader/NativeLibrary;+254 [email protected]
j  jdk.internal.loader.NativeLibraries.loadLibrary(Ljava/lang/Class;Ljava/io/File;)Ljdk/internal/loader/NativeLibrary;+51 [email protected]
j  java.lang.ClassLoader.loadLibrary(Ljava/lang/Class;Ljava/io/File;)Ljdk/internal/loader/NativeLibrary;+31 [email protected]
j  java.lang.Runtime.load0(Ljava/lang/Class;Ljava/lang/String;)V+61 [email protected]
j  java.lang.System.load(Ljava/lang/String;)V+7 [email protected]
j  io.github.humbleui.skija.impl.Library.load()V+176
j  io.github.humbleui.skija.impl.Library.staticLoad()V+19
j  io.github.humbleui.skija.Font.<clinit>()V+0
v  ~StubRoutines::call_stub 0x00007fbc8452ccc6
j  io.github.humbleui.jwm.examples.Example.<clinit>()V+34
v  ~StubRoutines::call_stub 0x00007fbc8452ccc6

Support for GrDirectContext::MakeGL with GrGLInterface

GrDirectContext provides the ability to pass in a GrGLInterface into the MakeGL method (as well as GrContextOptions). It would be useful if Skija added support for this method in DirectContext. This would allow better integration into existing applications using OpenGL that need more control over the OpenGL state. At the moment Skia will modify the GL state on its own making it harder for applications to keep track of it.

Clojure examples fail with ClassNotFoundException

On a fresh clone & local build:

$ cd examples/clojure && ./scripts/run.py
Syntax error (ClassNotFoundException) compiling at (lwjgl/main.clj:1:1).
io.github.humbleui.types.Rect

Full report at:
/tmp/clojure-1209162283782155536.edn

Traceback (most recent call last):
  File "/home/w1n5t0n/dev/lib/skija/examples/clojure/./script/run.py", line 11, in <module>
    sys.exit(main())
  File "/home/w1n5t0n/dev/lib/skija/examples/clojure/./script/run.py", line 7, in main
    subprocess.check_call(["clj", "-M:" + system, "-m", "lwjgl.main"])
  File "/usr/lib/python3.10/subprocess.py", line 369, in check_call
    raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command '['clj', '-M:linux', '-m', 'lwjgl.main']' returned non-zero exit status 1.

Same for clojure-snake.

Full report: https://pastebin.com/MGzbjChf

bitmap, jwm, and swt demos seem to work, others also fail but for different reasons.

Line metrics incorrect with UTF-16 surrogate pairs

The code

String[] families = {"DejaVu Sans Mono"};
FontCollection fc = new FontCollection();
fc.setDefaultFontManager(FontMgr.getDefault());
TextStyle ts = new TextStyle().setColor(0xffD2D2D2).setFontSize(20).setFontFamilies(families);
StrutStyle ss = new StrutStyle()
  .setFontFamilies(families)
  .setFontStyle(FontStyle.BOLD_ITALIC)
  .setFontSize(20)
  .setHeight(2)
  .setLeading(3)
  .setEnabled(true)
  .setHeightForced(true)
  .setHeightOverridden(true);
ParagraphStyle ps = new ParagraphStyle().setTextStyle(ts).setStrutStyle(ss);
ParagraphBuilder b = new ParagraphBuilder(ps, fc);
b.addText("𝕨𝕨𝕨𝕨𝕨");
Paragraph p = b.build();
b.close();
p.layout(Float.POSITIVE_INFINITY);
System.out.println(p.getLineMetrics()[0].getEndIndex());

prints 6. But each 𝕨 is two chars, so the expected result is 10.

64-bit Linux, Skija version 0.98.0

Rect size limit?

I can't draw a Rect with both width and height over 256

Doesn't work (doesn't get drawn):

canvas.drawRect(Rect.makeWH(300f, 300f), paint)

Fat/uber Jar doesn't work because of the multi-release feature

I'm trying to build a jar file for my Java program that depends on Skija with the help of the Gradle Shadow plugin. It builds fine, however, I can't run it, because it complains that sun.misc.Cleaner, referenced in Cleanable.java, doesn't exist. I can see that there are 2 versions of Cleanable.java, pre- and post-Java 9, and it looks like the pre-java-9 version ends up being used even though my JDK is newer.

What's the best strategy to create a working Jar file then? I'd be fine with one that doesn't work on Java versions before 9.

An errro UnsatisfiedLinkError

I got an error "java.lang.UnsatisfiedLinkError: io.github.humbleui.skija.impl.BufferUtil._nGetPointerFromByteBuffer(Ljava/nio/ByteBuffer;)J"
when I type "BufferUtil.getPointerFromByteBuffer(...)"
how dose it happens

Bottom of emoji and text misaligned on linux

When I draw text with emoji together on Paragraph,I find bottom alignment of emoji and text is fine on mac,but not on linux.

on mac:
image

on linux:
image

I would like to know what is causing this and how to make it behave the same on linux as it does on mac.

ps. I use the same font(AppleColorEmoji.ttc on mac) on both two system.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.