changeset 592:19d930f9e3f0

Add the screenshot feature, using P or Home like the original game.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Wed, 23 Apr 2014 19:19:32 +0200
parents 2dfa4aa135d2
children 974decb8df4f
files pytouhou/lib/_sdl.pxd pytouhou/lib/opengl.pxd pytouhou/lib/sdl.pxd pytouhou/lib/sdl.pyx pytouhou/ui/gamerunner.pyx pytouhou/ui/opengl/backend.pxd pytouhou/ui/opengl/backend.pyx pytouhou/ui/opengl/gamerenderer.pyx
diffstat 8 files changed, 57 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- a/pytouhou/lib/_sdl.pxd
+++ b/pytouhou/lib/_sdl.pxd
@@ -70,6 +70,7 @@ cdef extern from "SDL_scancode.h" nogil:
     ctypedef enum SDL_Scancode:
         SDL_SCANCODE_Z
         SDL_SCANCODE_X
+        SDL_SCANCODE_P
         SDL_SCANCODE_LSHIFT
         SDL_SCANCODE_UP
         SDL_SCANCODE_DOWN
@@ -77,6 +78,7 @@ cdef extern from "SDL_scancode.h" nogil:
         SDL_SCANCODE_RIGHT
         SDL_SCANCODE_LCTRL
         SDL_SCANCODE_ESCAPE
+        SDL_SCANCODE_HOME
 
 
 cdef extern from "SDL_events.h" nogil:
--- a/pytouhou/lib/opengl.pxd
+++ b/pytouhou/lib/opengl.pxd
@@ -56,6 +56,9 @@ cdef extern from 'epoxy/gl.h' nogil:
         GL_TEXTURE_MIN_FILTER
         GL_TEXTURE_MAG_FILTER
 
+    ctypedef enum GLenum_store 'GLenum':
+        GL_PACK_INVERT_MESA
+
     ctypedef enum GLenum_client_state 'GLenum':
         GL_COLOR_ARRAY
         GL_VERTEX_ARRAY
@@ -143,6 +146,8 @@ cdef extern from 'epoxy/gl.h' nogil:
     void glBindTexture(GLenum_textarget target, GLuint texture)
     void glTexParameteri(GLenum_textarget target, GLenum_texparam pname, GLint param)
     void glTexImage2D(GLenum_textarget target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum_format format_, GLenum_type type_, const GLvoid *data)
+    void glGetTexImage(GLenum_textarget target, GLint level, GLenum_format format_, GLenum_type type_, GLvoid *img)
+    void glPixelStorei(GLenum_store pname, GLint param)
 
     void glClearColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)
     void glClear(GLbitfield mask)
--- a/pytouhou/lib/sdl.pxd
+++ b/pytouhou/lib/sdl.pxd
@@ -33,6 +33,7 @@ cdef SDL_WindowFlags WINDOW_RESIZABLE
 #TODO: should be SDL_Scancode, but Cython doesn’t allow enum for array indexing.
 cdef long SCANCODE_Z
 cdef long SCANCODE_X
+cdef long SCANCODE_P
 cdef long SCANCODE_LSHIFT
 cdef long SCANCODE_UP
 cdef long SCANCODE_DOWN
@@ -40,6 +41,7 @@ cdef long SCANCODE_LEFT
 cdef long SCANCODE_RIGHT
 cdef long SCANCODE_LCTRL
 cdef long SCANCODE_ESCAPE
+cdef long SCANCODE_HOME
 
 cdef SDL_WindowEventID WINDOWEVENT_RESIZED
 
--- a/pytouhou/lib/sdl.pyx
+++ b/pytouhou/lib/sdl.pyx
@@ -33,6 +33,7 @@ WINDOW_RESIZABLE = SDL_WINDOW_RESIZABLE
 
 SCANCODE_Z = SDL_SCANCODE_Z
 SCANCODE_X = SDL_SCANCODE_X
+SCANCODE_P = SDL_SCANCODE_P
 SCANCODE_LSHIFT = SDL_SCANCODE_LSHIFT
 SCANCODE_UP = SDL_SCANCODE_UP
 SCANCODE_DOWN = SDL_SCANCODE_DOWN
@@ -40,6 +41,7 @@ SCANCODE_LEFT = SDL_SCANCODE_LEFT
 SCANCODE_RIGHT = SDL_SCANCODE_RIGHT
 SCANCODE_LCTRL = SDL_SCANCODE_LCTRL
 SCANCODE_ESCAPE = SDL_SCANCODE_ESCAPE
+SCANCODE_HOME = SDL_SCANCODE_HOME
 
 WINDOWEVENT_RESIZED = SDL_WINDOWEVENT_RESIZED
 
--- a/pytouhou/ui/gamerunner.pyx
+++ b/pytouhou/ui/gamerunner.pyx
@@ -108,8 +108,15 @@ cdef class GameRunner(Runner):
             self.renderer.start(self.common)
 
 
+    cdef void capture(self) except *:
+        if self.renderer is not None:
+            filename = 'screenshot/frame%06d.ppm' % self.game.frame
+            self.renderer.capture(filename, self.width, self.height)
+
+
     cpdef bint update(self) except? False:
         cdef long keystate
+        capture = False
 
         if self.background is not None:
             self.background.update(self.game.frame)
@@ -119,6 +126,8 @@ cdef class GameRunner(Runner):
                 scancode = event[1]
                 if scancode == sdl.SCANCODE_ESCAPE:
                     return False #TODO: implement the pause.
+                elif scancode == sdl.SCANCODE_P or scancode == sdl.SCANCODE_HOME:
+                    capture = True
             elif type_ == sdl.QUIT:
                 return False
             elif type_ == sdl.WINDOWEVENT:
@@ -172,4 +181,7 @@ cdef class GameRunner(Runner):
         if not self.skip and self.renderer is not None:
             self.renderer.render(self.game)
 
+        if capture:
+            self.capture()
+
         return True
--- a/pytouhou/ui/opengl/backend.pxd
+++ b/pytouhou/ui/opengl/backend.pxd
@@ -9,4 +9,5 @@ cdef bint use_debug_group
 cdef bint use_vao
 cdef bint use_framebuffer_blit
 cdef bint use_primitive_restart
+cdef bint use_pack_invert
 cdef bytes shader_header
--- a/pytouhou/ui/opengl/backend.pyx
+++ b/pytouhou/ui/opengl/backend.pyx
@@ -8,7 +8,8 @@ from pytouhou.lib.opengl cimport \
           GL_COLOR_ARRAY, GL_VERTEX_ARRAY, GL_TEXTURE_COORD_ARRAY,
           glPushDebugGroup, GL_DEBUG_SOURCE_APPLICATION, glPopDebugGroup,
           epoxy_gl_version, epoxy_is_desktop_gl, epoxy_has_gl_extension,
-          GL_PRIMITIVE_RESTART, glPrimitiveRestartIndex)
+          GL_PRIMITIVE_RESTART, glPrimitiveRestartIndex, glPixelStorei,
+          GL_PACK_INVERT_MESA)
 
 
 GameRenderer = None
@@ -47,7 +48,7 @@ def init(options):
 def discover_features():
     '''Discover which features are supported by our context.'''
 
-    global use_debug_group, use_vao, use_primitive_restart, shader_header
+    global use_debug_group, use_vao, use_primitive_restart, use_pack_invert, shader_header
 
     version = epoxy_gl_version()
     is_desktop = epoxy_is_desktop_gl()
@@ -56,6 +57,7 @@ def discover_features():
     use_vao = (is_desktop and version >= 30) or epoxy_has_gl_extension('GL_ARB_vertex_array_object')
     use_primitive_restart = (is_desktop and version >= 31)
     use_framebuffer_blit = (is_desktop and version >= 30)
+    use_pack_invert = epoxy_has_gl_extension('GL_MESA_pack_invert')
 
     if is_desktop:
         # gl_FragColor isn’t supported anymore starting with GLSL 4.2.
@@ -113,6 +115,9 @@ def create_window(title, x, y, width, he
         glEnable(GL_PRIMITIVE_RESTART)
         glPrimitiveRestartIndex(0xFFFF);
 
+    if use_pack_invert:
+        glPixelStorei(GL_PACK_INVERT_MESA, True)
+
     if use_debug_group:
         glPopDebugGroup()
 
--- a/pytouhou/ui/opengl/gamerenderer.pyx
+++ b/pytouhou/ui/opengl/gamerenderer.pyx
@@ -12,7 +12,7 @@
 ## GNU General Public License for more details.
 ##
 
-from libc.stdlib cimport free
+from libc.stdlib cimport malloc, free
 from itertools import chain
 
 from pytouhou.lib.opengl cimport \
@@ -21,14 +21,15 @@ from pytouhou.lib.opengl cimport \
           GL_FOG, GL_FOG_MODE, GL_LINEAR, GL_FOG_START, GL_FOG_END,
           GL_FOG_COLOR, GL_COLOR_BUFFER_BIT, GLfloat, glViewport, glScissor,
           GL_SCISSOR_TEST, GL_DEPTH_BUFFER_BIT, glPushDebugGroup,
-          GL_DEBUG_SOURCE_APPLICATION, glPopDebugGroup)
+          GL_DEBUG_SOURCE_APPLICATION, glPopDebugGroup, glBindTexture,
+          glGetTexImage, GL_TEXTURE_2D, GL_RGB, GL_UNSIGNED_BYTE)
 
 from pytouhou.utils.matrix cimport mul, new_identity
 from pytouhou.utils.maths cimport perspective, setup_camera, ortho_2d
 from pytouhou.game.text cimport NativeText, GlyphCollection
 from .shaders.eosd import GameShader, BackgroundShader, PassthroughShader
 from .renderer cimport Texture
-from .backend cimport is_legacy, use_debug_group
+from .backend cimport is_legacy, use_debug_group, use_pack_invert
 
 from collections import namedtuple
 Rect = namedtuple('Rect', 'x y w h')
@@ -105,6 +106,28 @@ cdef class GameRenderer(Renderer):
                 glPopDebugGroup()
 
 
+    def capture(self, filename, int width, int height):
+        capture_memory = <char*>malloc(width * height * 3)
+
+        glBindTexture(GL_TEXTURE_2D, self.framebuffer.texture)
+        glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, capture_memory)
+        glBindTexture(GL_TEXTURE_2D, 0)
+
+        # TODO: output to PNG instead.
+
+        # PPM output, bottom to top.
+        with open(filename, 'wb') as ppm:
+            ppm.write(('P6\n%d %d\n 255\n' % (width, height)).encode())
+            if use_pack_invert:
+                ppm.write(capture_memory[:width * height * 3])
+            else:
+                for i in range(width * (height - 1), -1, -width):
+                    ppm.write(capture_memory[i * 3:(i + width) * 3])
+
+        # Cleanup.
+        free(capture_memory)
+
+
     cdef void render_game(self, Game game):
         cdef long game_x, game_y
         cdef float x, y, z, dx, dy, dz