Skip to content

Add "from_existing_window" function to the _sdl2.Window #1837

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

Closed
wants to merge 20 commits into from

Conversation

yunline
Copy link
Contributor

@yunline yunline commented Feb 13, 2023

Add "from_existing_window" function to the _sdl2.Window

This PR is cloned from pygame/pygame#3685.

2023-02-06T21:59:19Z yunline wrote:

from_existing_window(hwnd: int) -> Window

With this function, pygame window could be inserted into another window (Tk/Qt/MFC/...) .
Here is an example based on Tkinter:

import tkinter
import threading

root=tkinter.Tk()
root.geometry("640x480")
btn=tkinter.Button(root,text="HelloWorld\n你好世界")
btn.pack()
cv=tkinter.Canvas(root)
cv.pack()

off=0
def pg_loop(hwnd):
    import pygame
    from pygame._sdl2 import video

    pygame.display.init()

    win=video.Window.from_existing_window(hwnd)
    
    win.focus()         # focus/restore is available
    #win.grab=1         # grab is available
    #win.opacity=0.5    # opacity is available
    #win.hide()         # hide/show is availabe
    #win.resizable=1    # available but works weirdly

    ren=video.Renderer(win)

    pos=(20,20)
    while not off:
        ren.draw_color=(0,0,0,0)
        ren.clear()
        ren.draw_color=(255,0,0,0)
        ren.fill_rect((*pos,10,10))
        ren.present()

        for event in pygame.event.get():
            if event.type==1024:#MOUSEMOTION
                pos=event.pos
            else:
                print(event)


if __name__ == "__main__":
    #hwnd=int(root.frame(),16)  # get the handle of the root window
    hwnd=cv.winfo_id()          # get the handle of the canvas widget

    p=threading.Thread(target=pg_loop,args=(hwnd,))

    p.start()
    root.mainloop()
    off=1

image

2023-01-27T19:18:18Z MyreMylar wrote:

Related Stale PR: pygame/pygame#2981

To reference when reviewing

2023-01-29T02:43:50Z yunline wrote:

Since the environment variable SDL_WINDOWID was no longer used in SDL2, I added a new hwnd argument to the set_mode() function to embed the pygame window to another window. I also wrote an example here

import tkinter
import threading

root=tkinter.Tk()
root.geometry("640x480")
cv=tkinter.Canvas(root,width=400,height=400)
cv.pack()

off=0
def pg_loop(hwnd):
    import pygame
    pygame.display.init()

    sf=pygame.display.set_mode(hwnd=hwnd)

    while not off:
        sf.fill((0,0,0))
        pygame.draw.rect(sf,(255,0,0),(10,10,10,10))
        pygame.display.update()

threading.Thread(target=pg_loop,args=(cv.winfo_id(),)).start()
root.mainloop()
off=1
```  > This script runs well.
However, it seems this feature is not compatible with OpenGL. When I try to embed the glcube into a tkinter window, it raises an exception like this: `pygame.error: The specified window isn't an OpenGL window`. Im not sure if it is a bug.  

2023-01-29T17:55:31Z MyreMylar wrote:

This script runs well. However, it seems this feature is not compatible with OpenGL. When I try to embed the glcube into a tkinter window, it raises an exception like this: pygame.error: The specified window isn't an OpenGL window. I’m not sure if it is a bug.

It is not a bug. You need an OpenGL enabled window to do any openGL rendering. You can create an OpenGL tkinter window with this library I believe:

https://pypi.org/project/pyopengltk/
https://github.com/jonwright/pyopengltk

There is also the SDL_HINT_VIDEO_WINDOW_SHARE_PIXEL_FORMAT to consider.

2023-01-30T08:50:24Z yunline wrote:

https://github.com/juyanith/Win32Host/blob/422833d3b77e58135ae9d493964902690774844f/samples/Sdl2Example/Sdl2Window.cs
https://gamedev.stackexchange.com/questions/110205/context-is-null-with-sdl-createwindowfrom-win32/119903#119903
These might be good references.

2023-01-30T08:46:03Z yunline wrote:

It is not a bug. You need an OpenGL enabled window to do any openGL rendering. You can create an OpenGL tkinter window with this library I believe:

https://pypi.org/project/pyopengltk/ https://github.com/jonwright/pyopengltk

There is also the SDL_HINT_VIDEO_WINDOW_SHARE_PIXEL_FORMAT to consider.

Thank you! This helps a lot. I modified the code and managed to run the glcube in a Tk window.
Here is an example of Pygame glcube embeded in a Tk window.

import tkinter
from OpenGL.GL import *
from OpenGL.GLU import *
from pyopengltk import OpenGLFrame
import pygame
from pygame.locals import *

# Define the vertices. usually a cube contains 8 vertices
vertices=( (1/2, -1/2, -1/2),
    (1/2, 1/2, -1/2),
    (-1/2, 1/2, -1/2),
    (-1/2, -1/2, -1/2),
    (1/2, -1/2, 1/2),
    (1/2, 1/2, 1/2),
    (-1/2, -1/2, 1/2),
    (-1/2, 1/2, 1/2))
# Define 12 edges for the body
edges = (
    (0,1),(0,3),
    (0,4),(2,1),
    (2,3),(2,7),
    (6,3),(6,4),
    (6,7),(5,1),
    (5,4),(5,7)
    )
# Define function to draw the cube
def Cube():
    glBegin(GL_LINES)
    for edge in edges:
        for index in edge:
            glVertex3fv(vertices[index])
    glEnd()

# Get Tk window and OpenGLFrame widget.
class AppOgl(OpenGLFrame):
    def initgl(self):pass
    def redraw(self):pass
root=tkinter.Tk()
root.geometry("640x640")
opengl_frame=AppOgl(root,width=600,height=600)
opengl_frame.pack()
off=0
def on_closing():
    global off
    root.destroy()
    off=1
root.protocol("WM_DELETE_WINDOW",on_closing)

# Main
def main():
    pygame.init()
    display=(600,600)

    pygame.display.set_mode(display, DOUBLEBUF | OPENGL, hwnd=opengl_frame.winfo_id())
    gluPerspective(45, (display[0] / display[1]), 0.1, 50.0)
    glTranslatef(0.0, 0.0, -5)
    while not off:
        root.update() # update tk window
        glRotatef(1,3,1,1)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        Cube()
        pygame.display.flip()
        pygame.time.wait(10)
        
main()

image

2023-01-30T19:39:51Z MyreMylar wrote:

I think that the new hwnd parameter could use a little bit of documentation in the display module docs for set_mode().
This file:
https://github.com/pygame/pygame/blob/main/docs/reST/ref/display.rst

It is possible we might want to re-create the old SDL_WINDOWID variable for backwards compatability (we could potentially add it even though it doesn't exist in SDL anymore, it would just be a little confusing) - but I'm not 100% convinced by that logic. We have now gone a pretty long time with SDL2 without recreating it, and honestly, it was a kind of clunky way to do it in Pygame 1. I personally prefer your new API with a parameter for set_mode() to achieve the same thing in a cleaner way.

2023-02-02T11:15:04Z yunline wrote:

OpenGL_Window is added to video module. Now we are able to create a window with OpenGL context created.

class OpenGL_Window(Window):
    def gl_flip() ->None : ...

By calling the gl_flip(), the window will be updated.

2023-02-02T12:31:43Z yunline wrote:

I merged some code from #3695 to pass the check.

2023-02-04T01:25:29Z chimosky wrote:

Tested this in sugar and it works as expected, the test activity starts and runs like it should.

2023-02-06T21:59:19Z Mega-JC wrote:

It's great to finally have this kind of functionality in pygame again.

One small remark: OpenGL_Window is a pretty non-standard way of naming classes in Python (mixing PascalCase with snake_case), especially if exposed to the end user. OpenGLWindow is just as clear and doesn't have that issue.

@MyreMylar MyreMylar closed this Feb 13, 2023
@MyreMylar MyreMylar reopened this Feb 13, 2023
@yunline yunline requested a review from a team as a code owner February 14, 2023 02:46
@MightyJosip MightyJosip added the _sdl2 pygame._sdl2 label Feb 14, 2023
@MyreMylar
Copy link
Member

I've just noticed:

SDL_HINT_VIDEO_FOREIGN_WINDOW_OPENGL 

Was recently added since SDL 2.0.22 which may solve this issue a bit more smoothly on very recent versions of SDL.

You can use:

#if SDL_VERSION_ATLEAST(2, 0, 22)
    // new code here
#else 
    // current code here
#endif

To make this possible.

I'm guessing it would be something like:

if (flags & PGS_OPENGL) {
    // Create window with SDL_CreateWindowFrom() and OpenGL
#if SDL_VERSION_ATLEAST(2, 0, 22)
    SDL_SetHint(SDL_HINT_VIDEO_FOREIGN_WINDOW_OPENGL, "1" );
    win = SDL_CreateWindowFrom(hwnd);
    SDL_SetHint(SDL_HINT_VIDEO_FOREIGN_WINDOW_OPENGL, "0" );
#else 
    // See https://gamedev.stackexchange.com/a/119903
    dummy = SDL_CreateWindow(
        "OpenGL Dummy", 0, 0, 1, 1,
        SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN);
    sprintf(dummy_id_str, "%p", dummy);
    SDL_SetHint(SDL_HINT_VIDEO_WINDOW_SHARE_PIXEL_FORMAT,
                dummy_id_str);
    
    win = SDL_CreateWindowFrom(hwnd);
    
    SDL_SetHint(SDL_HINT_VIDEO_WINDOW_SHARE_PIXEL_FORMAT,
                "");
#endif
}

Copy link
Member

@MyreMylar MyreMylar left a comment

Choose a reason for hiding this comment

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

As above. It would be good to get a neater version of the fix in for the far future when we eventually are able to drop support for pre-2.0.24 versions of sdl.

Copy link
Member

@MyreMylar MyreMylar left a comment

Choose a reason for hiding this comment

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

This has developed a merge conflict after recent merges.

@yunline yunline mentioned this pull request Feb 26, 2023
@yunline yunline force-pushed the from_existing_window-API branch from 89ca8eb to 695e638 Compare February 27, 2023 07:11
@yunline yunline force-pushed the from_existing_window-API branch from 9e63c75 to e9366d5 Compare February 27, 2023 10:42
@yunline
Copy link
Contributor Author

yunline commented Feb 28, 2023

OpenGLWindow should be another PR

@yunline
Copy link
Contributor Author

yunline commented Feb 28, 2023

Now SDL_WINDOWID is supported.
If the hwnd parameter is not passed, the SDL_WINDOWID will be checked.
Test code:

import tkinter
import threading
import os

root=tkinter.Tk()
root.geometry("640x480")
btn=tkinter.Button(root,text="HelloWorld\n你好世界")
btn.pack()
cv=tkinter.Canvas(root)
cv.pack()

off=0
def pg_loop(hwnd):
    import pygame
    from pygame._sdl2 import video

    os.environ['SDL_WINDOWID']=str(hwnd)

    pygame.display.init()

    sf=pygame.display.set_mode()

    pos=(20,20)
    while not off:
        sf.fill((0,0,0))
        pygame.draw.rect(sf,(255,0,0),(*pos,10,10))
        pygame.display.update()

        for event in pygame.event.get():
            if event.type==1024:#MOUSEMOTION
                pos=event.pos
            else:
                print(event)

if __name__ == "__main__":
    hwnd=cv.winfo_id()          # get the handle of the canvas widget

    p=threading.Thread(target=pg_loop,args=(hwnd,))

    p.start()
    root.mainloop()
    off=1
    

@yunline yunline changed the title Add "from_existing_window" function to the _sdl2.Window Support embedding pygame window to a foreign window Feb 28, 2023
@yunline yunline changed the title Support embedding pygame window to a foreign window Add "from_existing_window" function to the _sdl2.Window Feb 28, 2023
@yunline
Copy link
Contributor Author

yunline commented Feb 28, 2023

#1954

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
_sdl2 pygame._sdl2
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants