# HG changeset patch # User Emmanuel Gil Peyrot # Date 1398273572 -7200 # Node ID 19d930f9e3f037d8f7eb190d1f3a49ab32e670e2 # Parent 2dfa4aa135d22c5c14b02d8433e17062272e06a8 Add the screenshot feature, using P or Home like the original game. diff --git a/pytouhou/lib/_sdl.pxd b/pytouhou/lib/_sdl.pxd --- 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: diff --git a/pytouhou/lib/opengl.pxd b/pytouhou/lib/opengl.pxd --- 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) diff --git a/pytouhou/lib/sdl.pxd b/pytouhou/lib/sdl.pxd --- 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 diff --git a/pytouhou/lib/sdl.pyx b/pytouhou/lib/sdl.pyx --- 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 diff --git a/pytouhou/ui/gamerunner.pyx b/pytouhou/ui/gamerunner.pyx --- 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 diff --git a/pytouhou/ui/opengl/backend.pxd b/pytouhou/ui/opengl/backend.pxd --- 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 diff --git a/pytouhou/ui/opengl/backend.pyx b/pytouhou/ui/opengl/backend.pyx --- 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() diff --git a/pytouhou/ui/opengl/gamerenderer.pyx b/pytouhou/ui/opengl/gamerenderer.pyx --- 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 = 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