Skip to content

Instantly share code, notes, and snippets.

@ad8e
Last active November 14, 2024 17:29
Show Gist options
  • Save ad8e/dd150b775ae6aa4d5cf1a092e4713add to your computer and use it in GitHub Desktop.
Save ad8e/dd150b775ae6aa4d5cf1a092e4713add to your computer and use it in GitHub Desktop.
instructions to use skia and glfw together. (download, installation, first program). as of Sept 2023, Windows is broken but this is still sadly the best starting resource for skia on Windows too.
/* Note: this Google copyright notice only applies to the original file, which has large sections copy-pasted here. my changes are under CC0 (public domain).
* Copyright 2015 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
/*
The official instructions don't work well. These alternative instructions are intended to be the shortest path to get a minimal setup running.
The Linux steps were run through successfully on Sept 2023.
The Windows steps are known to be broken; the broken part is Step 7. The Include and Library directories should be tweaked.
This was made by copy-pasting and fixing two sources: https://github.com/google/skia/tree/master/experimental/GLFWTest and https://gist.github.com/zester/5163313
Don't bother trying these two sources; neither of them works.
step 1: install glfw (on Linux, "sudo apt install libglfw3-dev" will get you an acceptable (and outdated) version. on Visual Studio, you can get glfw from vcpkg.)
step 2: follow https://skia.org/docs/user/download/ to download and build skia.
Move forward to either Windows step 6 or Linux step 6.
Windows step 6, Visual Studio 2017:
if you're in Windows, you will need to use bash (tested as of 2017; haven't tried recently). on my system, a copy of bash came with my installation of Git for Windows.
cmd.exe doesn't allow single quotes, which are necessary to give the VC path. the various skia_use_foo commands are necessary to stop VS from erroring out when the headers are missing
run these two commands, replacing "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC" with your own VC directory:
gn gen out/Static --args='is_official_build=true win_vc="C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC" skia_use_libpng=false skia_use_zlib=false skia_use_libwebp=false skia_enable_pdf=false skia_use_libjpeg_turbo=false skia_use_expat=false'
ninja -C out/Static
Windows step 7. Warning, this is outdated. The massive "FOLDER1\x" was changed to not be necessary, but I haven't tested the correct steps now:
add this file to a new VS project
append "FOLDER1\skia\include\core;FOLDER1\skia\include\gpu;FOLDER1\skia\include\config;FOLDER1\skia\include\utils;FOLDER2\glfw\include'" to the VC include directories of your project, where FOLDERX represents the directories you put them in.
you must include all 4 skia folders because the files inside skia folders assume they see the other folders.
if you're unfamiliar with how the include directory works, it's in Project->Properties, VC++ Directories, Include Directories.
append "FOLDER1\skia\out\Static;FOLDER2\glfw\src\Debug;" to Library Directories, again replacing FOLDERX with the true location. add "opengl32.lib;skia.lib;glfw3.lib;" to Linker->Input->Additional Dependencies
Set build mode to x64.
Build! This will produce a debug mode binary.
If in the future you want a release mode binary, you will need to re-build glfw in release mode, and change the glfw library folder to FOLDER2\glfw\src\Release;
Linux step 6, Ubuntu 23.04. Sept 3, 2023:
Run:
sudo apt install clang libjpeg-dev libicu-dev libwebp-dev libfontconfig-dev
bin/gn gen out/Static --args='is_official_build=true cc="clang" cxx="clang++"'
ninja -C out/Static
Linux step 7:
download this file as "glfw_ship.cpp", and place it in the parent folder of the "skia" directory. (this just makes "-Iskia" in the right place)
g++ -g -std=c++1z glfw_ship.cpp -lskia -ldl -lpthread -ljpeg -lfreetype -lz -lpng -lglfw -lfontconfig -lwebp -lwebpmux -lwebpdemux -lGL -Iskia -Lskia/out/Static/
./a.out
eventually, you will want color-correct spaces, and there are 5 places below (Ctrl+F "enable correct color spaces"), where you should replace/uncomment lines to enable this.
warning: color-correct spaces don't work in VMWare, because mesa doesn't support it.
*/
#include "GLFW/glfw3.h"
#define SK_GANESH
#define SK_GL
#include "include/gpu/GrBackendSurface.h"
#include "include/gpu/GrDirectContext.h"
#include "include/gpu/gl/GrGLInterface.h"
#include "include/gpu/gl/GrGLAssembleInterface.h"
#include "include/gpu/ganesh/SkSurfaceGanesh.h"
#include "include/gpu/ganesh/gl/GrGLBackendSurface.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColorSpace.h"
#include "include/core/SkSurface.h"
#include <stdio.h>
#include <stdlib.h>
//uncomment the two lines below to enable correct color spaces
//#define GL_FRAMEBUFFER_SRGB 0x8DB9
//#define GL_SRGB8_ALPHA8 0x8C43
GrDirectContext* sContext = nullptr;
SkSurface* sSurface = nullptr;
void error_callback(int error, const char* description) {
fputs(description, stderr);
}
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) {
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
}
void init_skia(int w, int h) {
auto interface = GrGLMakeNativeInterface();
if (interface == nullptr) {
//backup plan. see https://gist.github.com/ad8e/dd150b775ae6aa4d5cf1a092e4713add?permalink_comment_id=4680136#gistcomment-4680136
interface = GrGLMakeAssembledInterface(
nullptr, (GrGLGetProc) * [](void*, const char* p) -> void* { return (void*)glfwGetProcAddress(p); });
}
sContext = GrDirectContext::MakeGL(interface).release();
GrGLFramebufferInfo framebufferInfo;
framebufferInfo.fFBOID = 0; // assume default framebuffer
// We are always using OpenGL and we use RGBA8 internal format for both RGBA and BGRA configs in OpenGL.
//(replace line below with this one to enable correct color spaces) framebufferInfo.fFormat = GL_SRGB8_ALPHA8;
framebufferInfo.fFormat = GL_RGBA8;
SkColorType colorType = kRGBA_8888_SkColorType;
GrBackendRenderTarget backendRenderTarget = GrBackendRenderTargets::MakeGL(w, h,
0, // sample count
0, // stencil bits
framebufferInfo);
//(replace line below with this one to enable correct color spaces) sSurface = SkSurfaces::WrapBackendRenderTarget(sContext, backendRenderTarget, kBottomLeft_GrSurfaceOrigin, colorType, SkColorSpace::MakeSRGB(), nullptr).release();
sSurface = SkSurfaces::WrapBackendRenderTarget(sContext, backendRenderTarget, kBottomLeft_GrSurfaceOrigin, colorType, nullptr, nullptr).release();
if (sSurface == nullptr) abort();
}
void cleanup_skia() {
delete sSurface;
delete sContext;
}
const int kWidth = 960;
const int kHeight = 640;
int main(void) {
GLFWwindow* window;
glfwSetErrorCallback(error_callback);
if (!glfwInit()) {
exit(EXIT_FAILURE);
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//(uncomment to enable correct color spaces) glfwWindowHint(GLFW_SRGB_CAPABLE, GL_TRUE);
glfwWindowHint(GLFW_STENCIL_BITS, 0);
//glfwWindowHint(GLFW_ALPHA_BITS, 0);
glfwWindowHint(GLFW_DEPTH_BITS, 0);
window = glfwCreateWindow(kWidth, kHeight, "Simple example", NULL, NULL);
if (!window) {
glfwTerminate();
exit(EXIT_FAILURE);
}
glfwMakeContextCurrent(window);
//(uncomment to enable correct color spaces) glEnable(GL_FRAMEBUFFER_SRGB);
init_skia(kWidth, kHeight);
glfwSwapInterval(1);
glfwSetKeyCallback(window, key_callback);
// Draw to the surface via its SkCanvas.
SkCanvas* canvas = sSurface->getCanvas(); // We don't manage this pointer's lifetime.
while (!glfwWindowShouldClose(window)) {
glfwWaitEvents();
SkPaint paint;
paint.setColor(SK_ColorWHITE);
canvas->drawPaint(paint);
paint.setColor(SK_ColorBLUE);
canvas->drawRect({100, 200, 300, 500}, paint);
sContext->flush();
glfwSwapBuffers(window);
}
cleanup_skia();
glfwDestroyWindow(window);
glfwTerminate();
exit(EXIT_SUCCESS);
}
@mmcjimsey26
Copy link

mmcjimsey26 commented Sep 4, 2023

@ad8e AFAIK the function you are looking for now exists in the SkSurfaces namespace as WrapBackendRenderTarget and has the exact same arguments:

https://github.com/google/skia/blob/main/include/gpu/ganesh/SkSurfaceGanesh.h#L158

EDIT: The constructor for GrBackendRenderTarget has also been replaced with GrBackendRenderTargets::MakeGL() in https://github.com/google/skia/blob/main/include/gpu/ganesh/gl/GrGLBackendSurface.h#L49

@ad8e
Copy link
Author

ad8e commented Sep 4, 2023

This gist is working again, thanks to @mmcjimsey26. Couldn't have done it without him pointing the way.

The shown graphic works fine on my current 2x-scaled monitor, in Linux. I guess High DPI issues are for Macs (and maybe Windows, which I haven't tested).

@mmcjimsey26
Copy link

Also, it may be worth noting that in some rare cases, GrGLMakeNativeInterface() returns null, causing MakeGL() to fail. Here is the code I use (that works most of the time) to get around this issue:

#include "include/gpu/gl/GrGLAssembleInterface.h"

auto interface = GrGLMakeAssembledInterface(nullptr, (GrGLGetProc)*[](void *, const char *p) -> void * {
    return (void *)glfwGetProcAddress(p);
});

This idea came from neovide's renderer, and from my experience it seems to only work in cases where GrGLMakeNativeInterface() fails, so it probably won't work here, but it may be helpful to other folks.

@ad8e
Copy link
Author

ad8e commented Sep 4, 2023

I have no devices to test GrGLMakeAssembledInterface on, but you know what you're talking about, so I added it.

@inkling07
Copy link

For anyone it doesn't work, you now have to GrDirectContext::MakeGL to GrDirectContexts::MakeGL.

@colonelzkiller
Copy link

colonelzkiller commented Apr 20, 2024

This example uses SFML for Window, Input and GL Context, Skia for Canvas and 2D Graphics on a GPU Surface and Yoga Layout Engine for Flexbox layout.

https://github.com/SFML/SFML
https://github.com/SFML/SFML/archive/refs/tags/2.6.1.zip

https://github.com/google/skia
https://github.com/google/skia/archive/refs/tags/canvaskit/0.34.0.zip

https://github.com/facebook/yoga
https://github.com/facebook/yoga/archive/refs/tags/v3.0.4.zip

 g++ -o test test.cpp -I/usr/include/skia -lskia -lsfml-graphics -lsfml-window -lsfml-system -lthorvg -lyogacore -lEGL -lGLESv2 

Example Code

#define SK_GL

// Compile command:
// This project uses the Skia graphics library along with SFML, EGL, and other libraries. 
// Compilation requires linking against these libraries as specified below.
// g++ -o test test.cpp -I/usr/include/skia -lskia -lsfml-graphics -lsfml-window -lsfml-system  -lyogacore -lEGL -lGLESv2 

#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>

#include <SFML/Graphics.hpp>
#include <memory>
#include <yoga/Yoga.h>
#include <cmath>  // Include for mathematical functions like sin and cos
#include <iostream>

#include <skia/include/gpu/GrDirectContext.h>
#include <skia/include/gpu/gl/GrGLInterface.h>
#include <skia/include/core/SkSurface.h>
#include <skia/include/core/SkCanvas.h>
#include <skia/include/core/SkPath.h>
#include <skia/include/core/SkPaint.h>
#include <skia/include/core/SkRect.h>
#include <skia/include/core/SkRRect.h>

// Function to create and return an SkRRect representing a rounded rectangle
SkRRect createRoundedRectangle(float x, float y, float width, float height,
                               float radiusTopLeft, float radiusTopRight,
                               float radiusBottomRight, float radiusBottomLeft) {
    // Define the rectangle from specified position and size
    SkRect rect = SkRect::MakeXYWH(x, y, width, height);

    // Define radii for each corner of the rectangle
    SkVector radii[4];
    radii[SkRRect::kUpperLeft_Corner] = SkVector::Make(radiusTopLeft, radiusTopLeft);
    radii[SkRRect::kUpperRight_Corner] = SkVector::Make(radiusTopRight, radiusTopRight);
    radii[SkRRect::kLowerRight_Corner] = SkVector::Make(radiusBottomRight, radiusBottomRight);
    radii[SkRRect::kLowerLeft_Corner] = SkVector::Make(radiusBottomLeft, radiusBottomLeft);

    // Create and return the rounded rectangle with individual corner radii
    SkRRect roundedRect;
    roundedRect.setRectRadii(rect, radii);

    return roundedRect;
}

int main() {
    // Initialize an SFML window
    sf::RenderWindow window(sf::VideoMode(800, 600), "Hydra v0.0.1");

    // Setup Skia OpenGL context
    sk_sp<const GrGLInterface> interface = GrGLMakeNativeInterface();
    sk_sp<GrDirectContext> context = GrDirectContext::MakeGL(interface);

    // Define the image properties and create a Skia GPU surface
    SkImageInfo imageinfo = SkImageInfo::MakeN32Premul(800, 600);
    sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(context.get(), SkBudgeted::kNo, imageinfo);

    // Ensure the GPU surface was created successfully
    if (!surface) {
        std::cerr << "Failed to create Skia GPU surface\n";
        return -1;
    }

    // Prepare the Skia drawing context
    context->flush();

    // Retrieve the canvas from the Skia surface
    SkCanvas* canvas = surface->getCanvas();

    // Activate SFML window context
    window.setActive(true);

    // Set up Yoga layout for UI elements
    YGNodeRef container = YGNodeNew();
    YGNodeStyleSetWidth(container, 800);
    YGNodeStyleSetHeight(container, 100);
    YGNodeStyleSetFlexDirection(container, YGFlexDirectionRow);
    YGNodeStyleSetPadding(container, YGEdgeAll, 20);

    // Create and configure child nodes for layout
    YGNodeRef child = YGNodeNew();
    YGNodeStyleSetFlexGrow(child, 1);  // Allow the child to grow and fill the container
    YGNodeStyleSetMargin(child, YGEdgeAll, 5);
    YGNodeInsertChild(container, child, 0);

    YGNodeRef child1 = YGNodeNew();
    YGNodeStyleSetFlexGrow(child1, 1);
    YGNodeStyleSetMargin(child1, YGEdgeAll, 5);
    YGNodeInsertChild(container, child1, 1);

    // Compute the layout for all children
    YGNodeCalculateLayout(container, YGUndefined, YGUndefined, YGDirectionLTR);

    // Main application loop
    while (window.isOpen()) {
        sf::Event event;
        while (window.pollEvent(event)) {
            if (event.type == sf::Event::Closed) {
                window.close();
            }

            // Handle window resizing
            if (event.type == sf::Event::Resized) {
                sf::FloatRect visibleArea(0, 0, event.size.width, event.size.height);
                window.setView(sf::View(visibleArea));
            }
        }

        // Clear window with a specific color
        window.clear(sf::Color(23, 23, 23));

        // Start drawing operations
        canvas->clear(SkColorSetARGB(255, 255, 255, 0));  // Clear with yellow color, full opacity

        // Draw rounded and regular rectangles based on Yoga layout
        SkRRect rect = createRoundedRectangle(YGNodeLayoutGetLeft(child), YGNodeLayoutGetTop(child), YGNodeLayoutGetWidth(child), YGNodeLayoutGetHeight(child), 0, 20, 0, 20);
        SkPaint paint;
        paint.setAntiAlias(true);  // Enable anti-aliasing for smoother edges
        paint.setColor(SK_ColorBLUE);
        canvas->drawRRect(rect, paint);

        SkRect rect1 = SkRect::MakeXYWH(YGNodeLayoutGetLeft(child1), YGNodeLayoutGetTop(child1), YGNodeLayoutGetWidth(child1), YGNodeLayoutGetHeight(child1));
        SkPaint paint1;
        paint1.setAntiAlias(true);
        paint1.setColor(SK_ColorRED);
        canvas->drawRect(rect1, paint1);
        
        // Finish drawing and update the canvas
        canvas->flush();

        // Display the updated window
        window.display();
    }

    // Cleanup
    delete canvas;

    return 0;
}

Screenshot

Screenshot from 2024-04-20 14-03-18

Shell Script to build skia

#!/bin/bash

# Set the PKG directory where everything will be installed
export PKG=$(pwd)/install

# Ensure the PKG directory exists
mkdir -pv $PKG

git clone 'https://chromium.googlesource.com/chromium/tools/depot_tools.git'
export PATH="${PWD}/depot_tools:${PATH}"

# Download the packages
wget https://github.com/google/skia/archive/refs/tags/canvaskit/0.34.0.tar.gz

tar -xvf 0.34.0.tar.gz

# Change directory to the glibc directory
cd skia-canvaskit-0.34.0

python3 tools/git-sync-deps

# Run configure with the adjusted paths
./bin/gn gen 'out/Shared' --args='
    is_official_build=true 
    is_component_build=true
    skia_enable_tools=false
    target_os="linux" 
    target_cpu="x64"
    skia_use_icu=false 
    skia_use_sfntly=false 
    skia_use_piex=true
    skia_use_system_expat=false 
    skia_use_system_freetype2=false 
    skia_use_system_libjpeg_turbo=false 
    skia_use_system_libpng=false 
    skia_use_system_libwebp=false 
    skia_use_system_zlib=false
    skia_enable_gpu=true'

ninja -C out/Shared

cd $PKG
mkdir -p usr/lib64
mkdir -p usr/include/skia

cd $PKG/usr/lib64
cp ../../../skia-canvaskit-0.34.0/out/Shared/*.a .
cp ../../../skia-canvaskit-0.34.0/out/Shared/*.so .

cd $PKG/usr/include/skia
cp -r ../../../../skia-canvaskit-0.34.0/include/* .

cd $PKG
#zip -r9 ../skia-0.34.0-1.pkg usr/lib64/
#zip -r9 ../skia-devel-0.34.0-1.pkg usr/include/

# Cleanup
#cd ..
#rm -rf 0.34.0.tar.gz install skia-canvaskit-0.34.0 depot_tools

@PieroCastillo
Copy link

this should work

#include "GLFW/glfw3.h"
#define SK_GANESH
#define SK_GL
#include "include/gpu/GrBackendSurface.h"
#include "include/gpu/GrDirectContext.h"
#include "include/gpu/gl/GrGLInterface.h"
#include "include/gpu/gl/GrGLAssembleInterface.h"
#include "include/gpu/ganesh/SkSurfaceGanesh.h"
#include "include/gpu/ganesh/gl/GrGLBackendSurface.h"
#include "include/gpu/ganesh/gl/GrGLDirectContext.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColorSpace.h"
#include "include/core/SkSurface.h"
#include "include/core/SkGraphics.h"

#include <stdio.h>
#include <stdlib.h>

//uncomment the two lines below to enable correct color spaces
//#define GL_FRAMEBUFFER_SRGB 0x8DB9
//#define GL_SRGB8_ALPHA8 0x8C43

GrDirectContext* sContext = nullptr;
SkSurface* sSurface = nullptr;

void error_callback(int error, const char* description) {
	fputs(description, stderr);
}

void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) {
	if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
		glfwSetWindowShouldClose(window, GL_TRUE);
}

void init_skia(int w, int h) {
	auto interface = GrGLMakeNativeInterface();
	if (interface == nullptr) {
		//backup plan. see https://gist.github.com/ad8e/dd150b775ae6aa4d5cf1a092e4713add?permalink_comment_id=4680136#gistcomment-4680136
		interface = GrGLMakeAssembledInterface(
			nullptr, (GrGLGetProc) * [](void*, const char* p) -> void* { return (void*)glfwGetProcAddress(p); });
	}
	sContext = GrDirectContexts::MakeGL(interface).release();

	GrGLFramebufferInfo framebufferInfo;
	framebufferInfo.fFBOID = 0; // assume default framebuffer
	// We are always using OpenGL and we use RGBA8 internal format for both RGBA and BGRA configs in OpenGL.
	//(replace line below with this one to enable correct color spaces) framebufferInfo.fFormat = GL_SRGB8_ALPHA8;
	framebufferInfo.fFormat = GL_RGBA8;

	SkColorType colorType = kRGBA_8888_SkColorType;
	GrBackendRenderTarget backendRenderTarget = GrBackendRenderTargets::MakeGL(w, h,
		0, // sample count
		0, // stencil bits
		framebufferInfo);

	//(replace line below with this one to enable correct color spaces) sSurface = SkSurfaces::WrapBackendRenderTarget(sContext, backendRenderTarget, kBottomLeft_GrSurfaceOrigin, colorType, SkColorSpace::MakeSRGB(), nullptr).release();
	sSurface = SkSurfaces::WrapBackendRenderTarget(sContext, backendRenderTarget, kBottomLeft_GrSurfaceOrigin, colorType, nullptr, nullptr).release();
	if (sSurface == nullptr) abort();
}

void cleanup_skia() {
	delete sSurface;
	delete sContext;
}

const int kWidth = 960;
const int kHeight = 640;


int main(void) {
	GLFWwindow* window;
	glfwSetErrorCallback(error_callback);
	if (!glfwInit()) {
		exit(EXIT_FAILURE);
	}

	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	//(uncomment to enable correct color spaces) glfwWindowHint(GLFW_SRGB_CAPABLE, GL_TRUE);
	glfwWindowHint(GLFW_STENCIL_BITS, 0);
	//glfwWindowHint(GLFW_ALPHA_BITS, 0);
	glfwWindowHint(GLFW_DEPTH_BITS, 0);

	window = glfwCreateWindow(kWidth, kHeight, "Simple example", NULL, NULL);
	if (!window) {
		glfwTerminate();
		exit(EXIT_FAILURE);
	}
	glfwMakeContextCurrent(window);
	//(uncomment to enable correct color spaces) glEnable(GL_FRAMEBUFFER_SRGB);

	init_skia(kWidth, kHeight);

	glfwSwapInterval(1);
	glfwSetKeyCallback(window, key_callback);

	// Draw to the surface via its SkCanvas.
	SkCanvas* canvas = sSurface->getCanvas(); // We don't manage this pointer's lifetime.

	while (!glfwWindowShouldClose(window)) {
		glfwWaitEvents();
		SkPaint paint;
        paint.setColor(SK_ColorWHITE);
        canvas->drawPaint(paint);
        paint.setColor(SK_ColorBLUE);
        canvas->drawRect({100, 200, 300, 500}, paint);
        sContext->flush();

		glfwSwapBuffers(window);
	}

	cleanup_skia();

	glfwDestroyWindow(window);
	glfwTerminate();
	exit(EXIT_SUCCESS);
}

but using vcpkg with cmake doesnt compile

@colonelzkiller
Copy link

colonelzkiller commented May 20, 2024

You haven't provided any information, such as the operating system you're using, the exact compiler errors, or any other relevant details. How is anyone supposed to help you figure out why it doesn't compile without that information?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment