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

Integrating skia #344

Merged
merged 36 commits into from
Jul 14, 2022
Merged

Integrating skia #344

merged 36 commits into from
Jul 14, 2022

Conversation

tushar5526
Copy link
Member

@tushar5526 tushar5526 commented Jun 11, 2022

Set up a basic rendering window using GLFW and skia, which supports the following APIs.

Environment

  • frameCount
  • deltaTime
  • Focused
  • cursor()
  • frameRate()
  • noCursor()
  • displayWidth
  • displayHeight
  • windowWidth
  • windowHeight
  • windowResized()
  • Width
  • Height
  • fullscreen()
  • pixelDensity()
  • displayDensity()

Structure

  • setup()
  • draw()
  • noLoop()
  • loop()
  • isLooping()
  • push()
  • pop()
  • redraw()

Event handlers

Keyboard

  • keyIsPressed
  • Key
  • keyCode (not used in p5py)
  • keyPressed()
  • keyReleased()
  • keyTyped() (needs to be fixed on vispy end)
  • keyIsDown() (not in p5py as of now, we can achieven this using key_is_pressed, so no need to implement it right now)

Mouse

  • movedX
  • movedY
  • mouseX
  • mouseY
  • pmouseX
  • pmouseY
  • winMouseX (no direct API in glfw, hacks might not work on all OS)
  • winMouseY (no direct API in glfw, hacks might not work on all OS)
  • pwinMouseY (no direct API in glfw, hacks might not work on all OS)
  • pwinMouseX (no direct API in glfw, hacks might not work on all OS)
  • mouseButton
  • mouseIsPressed
  • mouseMoved()
  • mouseDragged()
  • mousePressed()
  • mouseReleased()
  • mouseClicked()
  • doubleClicked()
  • mouseWheel()

Set up the rendering APIs for basic primitive shapes and set up the event handlers using glfw.

2D Primitives

  • arc()
  • ellipse()
  • circle()
  • line()
  • point()
  • quad()
  • rect()
  • square()
  • triangle()

glfw.MOUSE_BUTTON_MIDDLE: 3
}

class PseudoMouseEvent:
Copy link
Member Author

@tushar5526 tushar5526 Jun 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ziyaointl I am using a PseudoEventClass to mimic the data that was generated while using vispy-based events because the events.py is highly dependent on the event object passed by vispy.

Please share your thoughts on this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is solution has the pro of modifying minimal code in the current codebase, but I think in terms of keeping abstractions between renderers clean we may want to not include information about vispy inside of the skia renderer.

One solution I propose is to first modify the Event, MouseEvent, and KeyEvent classes in p5py so they don't directly rely on the raw events emitted by vispy, but rather specific information relavant from those events (e.g. event.pos, event.buttons). Then, one could write two helper functions that translate either vispy events or glfw events to p5py events. This seems to be similar to what @mrdaybird wrote in his proposal.

Let me your thoughts! If you also think this is a good idea, you can either create a new PR that refactors the existing event classes and rebase this skia PR on top of it, or you can keep the PseudoMouseEvent for now and create the refactor PR later down the road :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies for the late response, I was shifting back home for summer break.

Yes, this is a good idea. We should refactor the event classes. I will add a helper function in util to generate a p5py event according to the builtins.current_renderer we are using.

I will keep the PseudoMouseEvent now as it is working fine now and I could quickly move to the renderer implementation.

Also, the Renderer and Events modules are completely independent of each other so I am thinking of refactoring the events module later.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries at all! I think putting helper functions in either events.py or the respective renderers may be slightly better than putting them into the global util file, but that's just my opionon. The rest of the plan sounds great. Thank you!

@mrdaybird
Copy link

mrdaybird commented Jun 25, 2022

Checkout moderngl-window for how they implement event handling:
https://github.com/moderngl/moderngl-window/tree/master/moderngl_window/context

@tushar5526
Copy link
Member Author

Also, taking a look at moderngl-window, it seems like a great choice for us to later move on to. It would provide support for multiple windows(glfw, pyglet, pyqt)

Though it's a bit late for me to redo everything now, but we can explore migrating to it later for implementing other Processing APIs easily.

@tushar5526 tushar5526 force-pushed the integrating-skia branch 2 times, most recently from 3b198b0 to 0fe20aa Compare June 29, 2022 17:59
Current implementation of no_loop(), loop(), with proper frame_count
Setting up Sketch in base.py
Implemented a basic renderer module for testing purposes.
- Added skia to requirements.txt
- Refactor events.py, userspace.py and base.py
- Add handlers.py for defining callback handlers of GLFW
- Bug Fix in events.py
- Deprecated event.change

Mouse Events are integarted using the existing events system
- moved_x
- moved_y
- handler for on_mouse_motion

Bug Fixes:
- fixed a small bug in events.py MouseButton
- Integrate mouse_dragged()
- Added a dataclass to store the input state
- Ran black on few files for formatting
- Each renderer instance will have its own style stack
- All the style variables are now present in renderer.styles
- Each renderer has its own matrix stack now
- transforms.py uses the functions provided by the renderer to update matrix and style settings
- This was needed as skia had predefined function for rotate, transform, scale etc. and we didn't need to implement it manually
@tushar5526
Copy link
Member Author

Hey, @ziyaointl, I need your thoughts on this.

primitives.py defines the APIs like arc, ellipse etc. It pre-processes the input and then passes it on to the renderer for rendering.

So, if we have to draw an arc with RADIUS mode in Processing, primitives will handle adjusting the x, y, w, h values before passing it to the renderer.

The way we will pre-process will depend upon whether we are using skia or vispy, so we would need different pre-processing logic for each of them.

How should I achieve this, I am thinking of using if-else to check which renderer we are using and pre-process accordingly.

PS: The functions inside primitives.py are exported, so we can't do something like "conditional exports" because we get to know about the type of renderer later in the process.

Let me know your thoughts on this approach, or if you have some other design in your mind that would work.

- Arc
- Ellipse
- Circle
- Line
- primitves.py holds different logic to render primitives depending upon the current renderer.
- We support all parameters same as p5.js
- Border Radius is now possible!
- push_style
- pop_style
- pop_matrix
- push_matrix
@tushar5526 tushar5526 marked this pull request as ready for review July 5, 2022 19:34
@tushar5526
Copy link
Member Author

The 2D renderer looks great now, attaching some videos for comparison.

skia.mp4
vispy.mp4

@ziyaointl
Copy link
Member

@tushar5526 On preprocessing inputs before passing them into the renderer, I think it make a lot of sense to use if-else for now. After we figure out what different preprocessing needs to be done for each renderer, we can potentially tweak the renderer interface and try to move some of the renderer specific logic to each renderer class. But before we actually implement the preprocessing code for skia, it would be pretty hard to know what API tweaks we should make.

Thanks for posting the video! This is incredible progress! It's amazing that there is no aliasing at all. I look forward to the day that we can set skia as the default renderer.

@tushar5526
Copy link
Member Author

@ziyaointl should I wait for you to review and merge this PR, before continuing with my next milestone?

I was thinking of making a branch from this one, and working parallelly on my next milestone.

@ziyaointl
Copy link
Member

@tushar5526 Thanks for asking! Feel free to continue with another branch, and we can rebase as needed.

@tushar5526 tushar5526 mentioned this pull request Jul 7, 2022
27 tasks
ellipse([mx, my], radius, radius)

if __name__ == '__main__':
run(renderer='skia')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of duplicating the tests for each renderer, maybe we could write a function that parses the renderer selection via a command line argument and use that function in all the tests?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we should do that. Earlier I considered calling both the backend renderers one by one, something like

if __name__ == '__main__':
    run(renderer='skia')    
    run()

But, this was breaking in tests with noLoop enabled, because we are running the test for 100 frames and then calling exit().

I think it would be easier your way. I will make the updates to the testing suite.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have updated the testing suite, and the GHA for sanity tests will fail for the skia renderer now as we have not implemented some APIs in skia in this PR. We could either not run tests for skia.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think not running tests for skia is a reasonable approach for now.

@@ -375,3 +376,52 @@ def arc(self, *args):
def shape(self, vertices, contours, shape_type, *args):
"""Render a Pshape"""
self.render_shape(PShape(vertices=vertices, contours=contours, shape_type=shape_type))

def reset_transforms(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this (along with a few other functions) is duplicated between vispy2d and 3d as well. Could you also move them to the common opengl renderer?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the functions, and declared an abstract render function in openglrenderer for shape

@ziyaointl
Copy link
Member

@tushar5526 Thanks for such a quick response! I added one more small comment, but otherwise I think this PR is in great shape and should be merged :D

@ziyaointl ziyaointl merged commit 6900054 into p5py:master Jul 14, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants