Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix jitter behaviour of skia due to double buffers swapping #446

Merged
merged 7 commits into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 37 additions & 32 deletions p5/sketch/Skia2DRenderer/base.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import builtins
from p5.core import p5

import contextlib, glfw, skia
import skia
from OpenGL import GL
from time import time

import copy
from ..events import handler_names
Expand Down Expand Up @@ -76,20 +74,14 @@ def clean_up(self):
def glfw_window(self):
if not glfw.init():
raise RuntimeError("glfw.init() failed")

window = glfw.create_window(*self._size, "p5py", None, None)
glfw.make_context_current(window)
return window

def skia_surface(self, window=None, size=None):
def skia_surface(self):
self.context = skia.GrDirectContext.MakeGL()
if size:
width, height = size
elif window:
width, height = glfw.get_framebuffer_size(window)
else:
raise ValueError(
"Both window and size can't be None, This is probably an error within p5 instead of the sketch"
)
width, height = glfw.get_framebuffer_size(self.window)
backend_render_target = skia.GrBackendRenderTarget(
width,
height,
Expand All @@ -108,12 +100,10 @@ def skia_surface(self, window=None, size=None):
return surface

# create a new surface everytime
def create_surface(self, size=None):
if not size:
size = self._size
self._size = size
builtins.width, builtins.height = size
self.surface = self.skia_surface(self.window, size)
def create_surface(self):
self._size = glfw.get_framebuffer_size(self.window)
builtins.width, builtins.height = self._size
self.surface = self.skia_surface()
self.canvas = self.surface.getCanvas()
p5.renderer.initialize_renderer(self.canvas, self.paint, self.path)

Expand All @@ -133,8 +123,11 @@ def main_loop(self):
builtins.frame_count += 1
with self.surface as self.canvas:
self.draw_method()

p5.renderer._store_surface_state()
self.surface.flushAndSubmit()
glfw.swap_buffers(self.window)
p5.renderer._restore_surface_state()
last_render_call_time = time()

# If redraw == True, we have rendered the frame once
Expand All @@ -157,27 +150,39 @@ def start(self):
self.window = self.glfw_window()
self.create_surface()
self.assign_callbacks()

p5.renderer.initialize_renderer(self.canvas, self.paint, self.path)

# We don't draw the buffer from scratch each time, instead store the current state of surface
# and restore it after swapping the buffer
self.setup_method()
self.poll_events()
p5.renderer.render()

# Get snapshot of surface
p5.renderer._store_surface_state()

# Write to secondary buffer
self.surface.flushAndSubmit()
glfw.swap_buffers(self.window)

p5.renderer._restore_surface_state()
self.surface.flushAndSubmit()

# Buffers are swapped twice so that both buffers have the same initial surface state
glfw.swap_buffers(self.window)

self.main_loop()
self.clean_up()

def resize(self):
# when glfw changes the framebuffer size, we will be resized completely
# until then hold the rendering calls
self.resized = False

# call change the window size(), this will not be done instantly
# but after some time and a frame_buffer_changed callback will occur on
# on a different thread
glfw.set_window_size(self.window, *self.size)

# when glfw changes the framebuffer size, we will be resized completely
# until then hold the rendering calls
self.resized = False

def poll_events(self):
glfw.poll_events()
if glfw.get_key(
Expand Down Expand Up @@ -216,20 +221,16 @@ def frame_buffer_resize_callback_handler(self, window, width, height):
# Creates an Image of current surface and a copy of current style configurations
# For the purpose of handling setup_method() re-call
# Ref: Issue #419
old_image = self.surface.makeImageSnapshot()
old_image = old_image.resize(old_image.width(), old_image.height())
old_style = copy.deepcopy(p5.renderer.style)

GL.glViewport(0, 0, width, height)
self.create_surface(size=(width, height))
self.create_surface()
self.setup_method()
p5.renderer._store_surface_state()
self.surface.flushAndSubmit()
glfw.swap_buffers(self.window)

# Redraws Image on the canvas/ new frame buffer
# Previously stored style configurations are restored for
# discarding setup_method() style changes
p5.renderer.style = old_style
with self.surface as self.canvas:
self.canvas.drawImage(old_image, 0, 0)

# Tell the program, we have resized the frame buffer
# and do not rewind/clear the path
Expand All @@ -238,3 +239,7 @@ def frame_buffer_resize_callback_handler(self, window, width, height):

def _enqueue_event(self, handler_name, event):
self.handler_queue.append((self.handlers[handler_name], event))

def exit(self):
self.clean_up()
exit()
19 changes: 15 additions & 4 deletions p5/sketch/Skia2DRenderer/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@


import builtins
import platform
import glfw
from p5.core import p5
from p5.sketch.events import KeyEvent, MouseEvent
Expand Down Expand Up @@ -130,8 +131,7 @@ def __init__(


def on_mouse_button(window, button, action, mod):
pos = glfw.get_cursor_pos(window)

pos = _adjust_mouse_pos(window, glfw.get_cursor_pos(window))
if button < 3:
button = BUTTONMAP.get(button, 0)

Expand All @@ -152,7 +152,7 @@ def on_mouse_button(window, button, action, mod):


def on_mouse_scroll(window, x_off, y_off):
pos = glfw.get_cursor_pos(window)
pos = _adjust_mouse_pos(window, glfw.get_cursor_pos(window))
delta = (float(x_off), float(y_off))
event = PseudoMouseEvent(pos=pos, delta=delta, modifiers=input_state.modifiers)
mev = MouseEvent(event, active=builtins.mouse_is_pressed)
Expand All @@ -161,7 +161,9 @@ def on_mouse_scroll(window, x_off, y_off):


def on_mouse_motion(window, x, y):
event = PseudoMouseEvent(pos=(x, y), modifiers=input_state.modifiers)
event = PseudoMouseEvent(
_adjust_mouse_pos(window, (x, y)), modifiers=input_state.modifiers
)
mev = MouseEvent(event, active=builtins.mouse_is_pressed)

# Queue a 'mouse_dragged` or `mouse_moved` event, not both similar to p5.js
Expand Down Expand Up @@ -220,3 +222,12 @@ def on_window_focus(window, focused):

def on_close(window):
pass


def _adjust_mouse_pos(window, pos):
if platform.system() != "Darwin":
return pos
glfw.get_window_content_scale(window)
pos_x, pos_y = pos
mul_x, mul_y = glfw.get_window_content_scale(window)
return pos_x * mul_x, pos_y * mul_y
9 changes: 9 additions & 0 deletions p5/sketch/Skia2DRenderer/renderer2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ def __init__(self):
self.path = None
self.curve_tightness = 0
self.pimage = None
# used to store the surface state before swapping with secondary buffer
self._surface_image = None

# TODO: Optimise it using bitmap or pixmap
def _store_surface_state(self):
self._surface_image = self.canvas.getSurface().makeImageSnapshot()

def _restore_surface_state(self):
self.canvas.drawImage(self._surface_image, 0, 0)

# Transforms functions
def push_matrix(self):
Expand Down
3 changes: 1 addition & 2 deletions p5/sketch/userspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,8 +309,7 @@ def exit():
before exiting the sketch.

"""
if p5.sketch is not None and builtins.current_renderer == "vispy":
p5.sketch.exit()
p5.sketch.exit()


def no_cursor():
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ glfw>=2.5.9
numpy
Pillow==9.0.1
vispy==0.10.0
PyOpenGL-accelerate==3.1.6
PyOpenGL-accelerate==3.1.7
PyOpenGL==3.1.6
requests>=2.25.0
dataclasses;python_version=="3.6"
Expand Down
Loading