|
| 1 | +#!/usr/bin/env python |
| 2 | +# -*- coding: utf-8 -*- |
| 3 | + |
| 4 | +# Copyright 2025, Hin-Tak Leung |
| 5 | + |
| 6 | +# SDL2 Re-write / simplication |
| 7 | + |
| 8 | +# See https://github.com/HinTak/skia-python-examples/issues/6 |
| 9 | + |
| 10 | +from sdl2 import \ |
| 11 | + SDLK_ESCAPE, SDL_CreateWindow, SDL_DestroyWindow, \ |
| 12 | + SDL_QUIT, \ |
| 13 | + SDL_Event, SDL_GL_CONTEXT_MAJOR_VERSION, SDL_GL_CONTEXT_MINOR_VERSION, \ |
| 14 | + SDL_GL_CONTEXT_PROFILE_CORE, SDL_GL_CONTEXT_PROFILE_MASK, \ |
| 15 | + SDL_GL_CreateContext, SDL_GL_DEPTH_SIZE, SDL_GL_DOUBLEBUFFER, \ |
| 16 | + SDL_GL_DeleteContext, \ |
| 17 | + SDL_GL_GetAttribute, SDL_GL_MakeCurrent, SDL_GL_STENCIL_SIZE, \ |
| 18 | + SDL_GL_SetAttribute, SDL_GL_SetSwapInterval, SDL_GL_SwapWindow, \ |
| 19 | + SDL_GetError, SDL_GetWindowSizeInPixels, \ |
| 20 | + SDL_INIT_VIDEO, SDL_Init, SDL_PollEvent, SDL_Quit, \ |
| 21 | + SDL_WINDOWPOS_CENTERED, \ |
| 22 | + SDL_WINDOW_OPENGL |
| 23 | + |
| 24 | +import skia |
| 25 | +from OpenGL import GL |
| 26 | +import ctypes |
| 27 | + |
| 28 | +width, height = 640, 480 |
| 29 | +title = b"Skia + PySDL2 Example" |
| 30 | + |
| 31 | +def main(): |
| 32 | + if SDL_Init(SDL_INIT_VIDEO) != 0: |
| 33 | + raise RuntimeError(f"SDL_Init Error: {SDL_GetError()}") |
| 34 | + |
| 35 | + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3) |
| 36 | + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3) |
| 37 | + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE) |
| 38 | + |
| 39 | + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1) |
| 40 | + |
| 41 | + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0) |
| 42 | + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8) |
| 43 | + |
| 44 | + window = SDL_CreateWindow( |
| 45 | + title, |
| 46 | + SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, |
| 47 | + width, height, |
| 48 | + SDL_WINDOW_OPENGL |
| 49 | + ) |
| 50 | + if not window: |
| 51 | + raise RuntimeError(f"SDL_CreateWindow Error: {SDL_GetError()}") |
| 52 | + |
| 53 | + gl_context = SDL_GL_CreateContext(window) |
| 54 | + if not gl_context: |
| 55 | + SDL_DestroyWindow(window) |
| 56 | + raise RuntimeError(f"SDL_GL_CreateContext Error: {SDL_GetError()}") |
| 57 | + |
| 58 | + if SDL_GL_MakeCurrent(window, gl_context) !=0: |
| 59 | + SDL_GL_DeleteContext(gl_context) |
| 60 | + SDL_DestroyWindow(window) |
| 61 | + raise RuntimeError(f"SDL_GL_MakeCurrent Error: {SDL_GetError()}") |
| 62 | + |
| 63 | + context = skia.GrDirectContext.MakeGL() |
| 64 | + if not context: |
| 65 | + raise RuntimeError("Failed to create Skia GrDirectContext") |
| 66 | + |
| 67 | + fb_width, fb_height = ctypes.c_int(), ctypes.c_int() |
| 68 | + SDL_GetWindowSizeInPixels(window, ctypes.byref(fb_width), ctypes.byref(fb_height)) |
| 69 | + |
| 70 | + # Check how many stencil bits we actually got |
| 71 | + stencil_bits = ctypes.c_int() |
| 72 | + SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, ctypes.byref(stencil_bits)) |
| 73 | + assert stencil_bits.value == 8 |
| 74 | + |
| 75 | + backend_render_target = skia.GrBackendRenderTarget( |
| 76 | + fb_width.value, |
| 77 | + fb_height.value, |
| 78 | + 0, # sampleCnt (MSAA samples) | samples - usually 0 for direct to screen |
| 79 | + stencil_bits.value, # stencilBits - Use value retrieved from GL context |
| 80 | + # Framebuffer ID 0 means the default window framebuffer |
| 81 | + skia.GrGLFramebufferInfo(0, GL.GL_RGBA8) # Target format GL_RGBA8 | Use 0 for framebuffer object ID for default framebuffer |
| 82 | + ) |
| 83 | + |
| 84 | + surface = skia.Surface.MakeFromBackendRenderTarget( |
| 85 | + context, backend_render_target, skia.kBottomLeft_GrSurfaceOrigin, |
| 86 | + skia.kRGBA_8888_ColorType, skia.ColorSpace.MakeSRGB()) |
| 87 | + |
| 88 | + if surface is None: |
| 89 | + context.abandonContext() |
| 90 | + raise RuntimeError("Failed to create Skia Surface from BackendRenderTarget") |
| 91 | + |
| 92 | + running = True |
| 93 | + event = SDL_Event() # Create event structure once |
| 94 | + |
| 95 | + while running: |
| 96 | + while SDL_PollEvent(event): |
| 97 | + if event.type == SDL_QUIT: |
| 98 | + print("Quit event received.") |
| 99 | + running = False |
| 100 | + break |
| 101 | + |
| 102 | + if not running: |
| 103 | + break |
| 104 | + |
| 105 | + GL.glClearColor(0.0, 0.0, 0.0, 1.0) # Black background |
| 106 | + GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_STENCIL_BUFFER_BIT) |
| 107 | + |
| 108 | + with surface as canvas: |
| 109 | + # Example drawing: Clear with Skia color, draw circle |
| 110 | + canvas.clear(skia.ColorWHITE) |
| 111 | + paint = skia.Paint( |
| 112 | + Color=skia.ColorBLUE, |
| 113 | + StrokeWidth=2, |
| 114 | + Style=skia.Paint.kStroke_Style, # Make it an outline |
| 115 | + AntiAlias=True |
| 116 | + ) |
| 117 | + canvas.drawCircle(width / 2, height / 2, 100, paint) |
| 118 | + |
| 119 | + paint.setColor(skia.ColorGREEN) |
| 120 | + paint.setStyle(skia.Paint.kFill_Style) # Fill style |
| 121 | + canvas.drawRect(skia.Rect.MakeXYWH(20, 20, 80, 80), paint) |
| 122 | + |
| 123 | + surface.flushAndSubmit() |
| 124 | + |
| 125 | + SDL_GL_SwapWindow(window) |
| 126 | + |
| 127 | + context.abandonContext() |
| 128 | + |
| 129 | + if gl_context: |
| 130 | + SDL_GL_DeleteContext(gl_context) |
| 131 | + if window: |
| 132 | + SDL_DestroyWindow(window) |
| 133 | + |
| 134 | + SDL_Quit() |
| 135 | + |
| 136 | +if __name__ == '__main__': |
| 137 | + main() |
0 commit comments