# HG changeset patch # User Emmanuel Gil Peyrot # Date 1386204939 -3600 # Node ID b39ad30c6620768ab6ae029381e44396483ea181 # Parent 2e8ceaa85d5c782febea03d8a05488e5e4c86ed8 Add a pure SDL backend. diff --git a/eosd b/eosd --- a/eosd +++ b/eosd @@ -49,6 +49,7 @@ parser.add_argument('--game', metavar='G parser.add_argument('--port', metavar='PORT', type=int, default=0, help='Local port to use for netplay') parser.add_argument('--remote', metavar='REMOTE', default=None, help='Remote address') parser.add_argument('--no-friendly-fire', action='store_false', help='Allow friendly-fire during netplay.') +parser.add_argument('--backend', metavar='BACKEND', choices=['opengl', 'sdl'], default='opengl', help='Which backend to use (opengl or sdl for now).') args = parser.parse_args() @@ -56,11 +57,17 @@ args = parser.parse_args() import sys import logging +if args.backend == 'opengl': + from pytouhou.ui.gamerenderer import GameRenderer + opengl = True +elif args.backend == 'sdl': + from pytouhou.ui.sdl.gamerenderer import GameRenderer + opengl = False + from pytouhou.lib.sdl import SDL from pytouhou.ui.window import Window from pytouhou.resource.loader import Loader from pytouhou.ui.gamerunner import GameRunner -from pytouhou.ui.gamerenderer import GameRenderer from pytouhou.game.player import GameOver from pytouhou.formats.t6rp import T6RP, Level from pytouhou.utils.random import Random @@ -167,7 +174,7 @@ def main(window, path, data, stage_num, game_class = GameBossRush if boss_rush else Game common = Common(resource_loader, characters, continues, stage_num - 1) - renderer = GameRenderer(resource_loader, window.use_fixed_pipeline) + renderer = GameRenderer(resource_loader, window) runner = GameRunner(window, renderer, common, resource_loader, skip_replay, con) window.set_runner(runner) @@ -250,7 +257,7 @@ def main(window, path, data, stage_num, with SDL(): window = Window(double_buffer=(not args.single_buffer), fps_limit=args.fps_limit, - fixed_pipeline=args.fixed_pipeline) + fixed_pipeline=args.fixed_pipeline, opengl=opengl) main(window, args.path, tuple(args.data), args.stage, args.rank, args.character, args.replay, args.save_replay, args.skip_replay, diff --git a/pytouhou/lib/_sdl.pxd b/pytouhou/lib/_sdl.pxd --- a/pytouhou/lib/_sdl.pxd +++ b/pytouhou/lib/_sdl.pxd @@ -42,7 +42,6 @@ cdef extern from "SDL_video.h" nogil: ctypedef enum SDL_WindowFlags: SDL_WINDOWPOS_CENTERED SDL_WINDOW_OPENGL - SDL_WINDOW_SHOWN SDL_WINDOW_RESIZABLE ctypedef struct SDL_Window: @@ -192,3 +191,35 @@ cdef extern from "SDL_ttf.h" nogil: TTF_Font *TTF_OpenFont(const char *filename, int ptsize) void TTF_CloseFont(TTF_Font *font) SDL_Surface *TTF_RenderUTF8_Blended(TTF_Font *font, const char *text, SDL_Color fg) + + +cdef extern from "SDL_blendmode.h" nogil: + ctypedef enum SDL_BlendMode: + SDL_BLENDMODE_NONE + SDL_BLENDMODE_BLEND + SDL_BLENDMODE_ADD + SDL_BLENDMODE_MOD + + +cdef extern from "SDL_render.h" nogil: + ctypedef struct SDL_Renderer: + pass + + ctypedef struct SDL_Texture: + pass + + ctypedef struct SDL_Point: + pass + + SDL_Renderer *SDL_CreateRenderer(SDL_Window *window, int index, Uint32 flags) + void SDL_RenderPresent(SDL_Renderer *renderer) + int SDL_RenderClear(SDL_Renderer *renderer) + SDL_Texture *SDL_CreateTextureFromSurface(SDL_Renderer *renderer, SDL_Surface *surface) + int SDL_RenderCopy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *srcrect, const SDL_Rect *dstrect) + int SDL_RenderCopyEx(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *srcrect, const SDL_Rect *dstrect, double angle, const SDL_Point *center, bint flip) + int SDL_RenderSetClipRect(SDL_Renderer *renderer, const SDL_Rect *rect) + int SDL_RenderSetViewport(SDL_Renderer *renderer, const SDL_Rect *rect) + + int SDL_SetTextureColorMod(SDL_Texture *texture, Uint8 r, Uint8 g, Uint8 b) + int SDL_SetTextureAlphaMod(SDL_Texture *texture, Uint8 alpha) + int SDL_SetTextureBlendMode(SDL_Texture *texture, SDL_BlendMode blend_mode) diff --git a/pytouhou/lib/sdl.pxd b/pytouhou/lib/sdl.pxd --- a/pytouhou/lib/sdl.pxd +++ b/pytouhou/lib/sdl.pxd @@ -46,11 +46,37 @@ cdef SDL_EventType WINDOWEVENT cdef class Window: cdef SDL_Window *window cdef SDL_GLContext context + cdef SDL_Renderer *renderer cdef void gl_create_context(self) except * - cdef void gl_swap_window(self) nogil + cdef void present(self) nogil cdef void set_window_size(self, int width, int height) nogil + # The following functions are there for the pure SDL backend. + cdef void create_renderer(self, Uint32 flags) + cdef void render_clear(self) + cdef void render_copy(self, Texture texture, Rect srcrect, Rect dstrect) + cdef void render_copy_ex(self, Texture texture, Rect srcrect, Rect dstrect, double angle, bint flip) + cdef void render_set_clip_rect(self, Rect rect) + cdef void render_set_viewport(self, Rect rect) + cdef Texture create_texture_from_surface(self, Surface surface) + + +cdef class Texture: + cdef SDL_Texture *texture + + cpdef set_color_mod(self, Uint8 r, Uint8 g, Uint8 b) + cpdef set_alpha_mod(self, Uint8 alpha) + cpdef set_blend_mode(self, SDL_BlendMode blend_mode) + + +cdef class Rect: + cdef SDL_Rect rect + + +cdef class Color: + cdef SDL_Color color + cdef class Surface: cdef SDL_Surface *surface diff --git a/pytouhou/lib/sdl.pyx b/pytouhou/lib/sdl.pyx --- a/pytouhou/lib/sdl.pyx +++ b/pytouhou/lib/sdl.pyx @@ -24,7 +24,6 @@ GL_DEPTH_SIZE = SDL_GL_DEPTH_SIZE WINDOWPOS_CENTERED = SDL_WINDOWPOS_CENTERED WINDOW_OPENGL = SDL_WINDOW_OPENGL -WINDOW_SHOWN = SDL_WINDOW_SHOWN WINDOW_RESIZABLE = SDL_WINDOW_RESIZABLE SCANCODE_Z = SDL_SCANCODE_Z @@ -96,12 +95,86 @@ cdef class Window: if self.context == NULL: raise SDLError(SDL_GetError()) - cdef void gl_swap_window(self) nogil: - SDL_GL_SwapWindow(self.window) + cdef void present(self) nogil: + if self.renderer == NULL: + SDL_GL_SwapWindow(self.window) + else: + SDL_RenderPresent(self.renderer) cdef void set_window_size(self, int width, int height) nogil: SDL_SetWindowSize(self.window, width, height) + # The following functions are there for the pure SDL backend. + cdef void create_renderer(self, Uint32 flags): + self.renderer = SDL_CreateRenderer(self.window, -1, flags) + if self.renderer == NULL: + raise SDLError(SDL_GetError()) + + cdef void render_clear(self): + ret = SDL_RenderClear(self.renderer) + if ret == -1: + raise SDLError(SDL_GetError()) + + cdef void render_copy(self, Texture texture, Rect srcrect, Rect dstrect): + ret = SDL_RenderCopy(self.renderer, texture.texture, &srcrect.rect, &dstrect.rect) + if ret == -1: + raise SDLError(SDL_GetError()) + + cdef void render_copy_ex(self, Texture texture, Rect srcrect, Rect dstrect, double angle, bint flip): + ret = SDL_RenderCopyEx(self.renderer, texture.texture, &srcrect.rect, &dstrect.rect, angle, NULL, flip) + if ret == -1: + raise SDLError(SDL_GetError()) + + cdef void render_set_clip_rect(self, Rect rect): + ret = SDL_RenderSetClipRect(self.renderer, &rect.rect) + if ret == -1: + raise SDLError(SDL_GetError()) + + cdef void render_set_viewport(self, Rect rect): + ret = SDL_RenderSetViewport(self.renderer, &rect.rect) + if ret == -1: + raise SDLError(SDL_GetError()) + + cdef Texture create_texture_from_surface(self, Surface surface): + texture = Texture() + texture.texture = SDL_CreateTextureFromSurface(self.renderer, surface.surface) + if texture.texture == NULL: + raise SDLError(SDL_GetError()) + return texture + + +cdef class Texture: + cpdef set_color_mod(self, Uint8 r, Uint8 g, Uint8 b): + ret = SDL_SetTextureColorMod(self.texture, r, g, b) + if ret == -1: + raise SDLError(SDL_GetError()) + + cpdef set_alpha_mod(self, Uint8 alpha): + ret = SDL_SetTextureAlphaMod(self.texture, alpha) + if ret == -1: + raise SDLError(SDL_GetError()) + + cpdef set_blend_mode(self, SDL_BlendMode blend_mode): + ret = SDL_SetTextureBlendMode(self.texture, blend_mode) + if ret == -1: + raise SDLError(SDL_GetError()) + + +cdef class Rect: + def __init__(self, int x, int y, int w, int h): + self.rect.x = x + self.rect.y = y + self.rect.w = w + self.rect.h = h + + +cdef class Color: + def __init__(self, Uint8 b, Uint8 g, Uint8 r, Uint8 a=255): + self.color.r = r + self.color.g = g + self.color.b = b + self.color.a = a + cdef class Surface: def __dealloc__(self): diff --git a/pytouhou/ui/gamerenderer.pyx b/pytouhou/ui/gamerenderer.pyx --- a/pytouhou/ui/gamerenderer.pyx +++ b/pytouhou/ui/gamerenderer.pyx @@ -32,8 +32,8 @@ Color = namedtuple('Color', 'r g b a') cdef class GameRenderer(Renderer): - def __init__(self, resource_loader, use_fixed_pipeline): - self.use_fixed_pipeline = use_fixed_pipeline #XXX + def __init__(self, resource_loader, window): + self.use_fixed_pipeline = window.use_fixed_pipeline #XXX Renderer.__init__(self, resource_loader) diff --git a/pytouhou/ui/sdl/__init__.py b/pytouhou/ui/sdl/__init__.py new file mode 100644 diff --git a/pytouhou/ui/sdl/gamerenderer.pxd b/pytouhou/ui/sdl/gamerenderer.pxd new file mode 100644 --- /dev/null +++ b/pytouhou/ui/sdl/gamerenderer.pxd @@ -0,0 +1,15 @@ +from pytouhou.game.game cimport Game +from .texture cimport TextureManager +from pytouhou.ui.window cimport Window + +cdef class GameRenderer: + cdef Window window + cdef TextureManager texture_manager + #cdef FontManager font_manager + cdef long x, y, width, height + + cdef public size #XXX + + cdef void render_game(self, Game game) except * + cdef void render_text(self, texts) except * + cdef void render_interface(self, interface, game_boss) except * diff --git a/pytouhou/ui/sdl/gamerenderer.py b/pytouhou/ui/sdl/gamerenderer.py new file mode 100644 --- /dev/null +++ b/pytouhou/ui/sdl/gamerenderer.py @@ -0,0 +1,136 @@ +# -*- encoding: utf-8 -*- +## +## Copyright (C) 2011 Thibaut Girka +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## + +from itertools import chain + +from pytouhou.lib.sdl import Rect +from .sprite import get_sprite_rendering_data + +from pytouhou.utils.helpers import get_logger +logger = get_logger(__name__) + + +class GameRenderer(object): + def __init__(self, resource_loader, window): + self.window = window + self.texture_manager = TextureManager(resource_loader, self.window.win) + + + def load_textures(self, anms): + self.texture_manager.load(anms) + + + def load_background(self, background): + if background is not None: + logger.error('Background rendering unavailable in the SDL backend.') + + + def start(self, common): + pass + + + def render(self, game): + self.render_game(game) + self.render_text(game.texts + game.native_texts) + self.render_interface(game.interface, game.boss) + + + def render_game(self, game): + x, y = game.interface.game_pos + self.window.win.render_set_viewport(Rect(x, y, game.width, game.height)) + self.window.win.render_set_clip_rect(Rect(x, -y, game.width, game.height)) + + if game is not None: + if game.spellcard_effect is not None: + self.render_elements([game.spellcard_effect]) + else: + self.window.win.render_clear() + + self.render_elements([enemy for enemy in game.enemies if enemy.visible]) + self.render_elements(game.effects) + self.render_elements(chain(game.players_bullets, + game.lasers_sprites(), + game.players, + game.msg_sprites())) + self.render_elements(chain(game.bullets, game.lasers, + game.cancelled_bullets, game.items, + game.labels)) + + + def render_interface(self, interface, boss): + interface.labels['framerate'].set_text('%.2ffps' % self.window.clock.get_fps()) + + self.window.win.render_set_viewport(Rect(0, 0, interface.width, interface.height)) + self.window.win.render_set_clip_rect(Rect(0, 0, interface.width, interface.height)) + + items = [item for item in interface.items if item.anmrunner and item.anmrunner.running] + labels = interface.labels.values() + + if items: + # Redraw all the interface + self.render_elements(items) + else: + # Redraw only changed labels + labels = [label for label in labels if label.changed] + + self.render_elements(interface.level_start) + + if boss: + self.render_elements(interface.boss_items) + + self.render_elements(labels) + for label in labels: + label.changed = False + + + def render_elements(self, elements): + nb_vertices = 0 + + objects = chain(*[element.objects for element in elements]) + for element in objects: + if nb_vertices >= MAX_ELEMENTS - 4: + break + + sprite = element.sprite + if sprite and sprite.visible: + ox, oy = element.x, element.y + blendfunc, (vertices, uvs, colors, rotation, flip) = get_sprite_rendering_data(sprite) + + # Pack data in buffer + x, y, width, height = vertices + left, right, bottom, top = uvs + r, g, b, a = colors #TODO: use it. + + #XXX + texture_width = 256 + texture_height = 256 + + source = Rect(left * texture_width, bottom * texture_height, (right - left) * texture_width, (top - bottom) * texture_height) + dest = Rect(ox + x, oy + y, width, height) + + texture = sprite.anm.texture + texture.set_color_mod(r, g, b) + texture.set_alpha_mod(a) + texture.set_blend_mode(2 if blendfunc else 1) + + if rotation or flip: + self.window.win.render_copy_ex(texture, source, dest, rotation, flip) + else: + self.window.win.render_copy(texture, source, dest) + + nb_vertices += 4 + + + def render_text(self, texts): + pass diff --git a/pytouhou/ui/sdl/sprite.pxd b/pytouhou/ui/sdl/sprite.pxd new file mode 100644 --- /dev/null +++ b/pytouhou/ui/sdl/sprite.pxd @@ -0,0 +1,3 @@ +from pytouhou.game.sprite cimport Sprite + +cpdef object get_sprite_rendering_data(Sprite sprite) diff --git a/pytouhou/ui/sdl/sprite.pyx b/pytouhou/ui/sdl/sprite.pyx new file mode 100644 --- /dev/null +++ b/pytouhou/ui/sdl/sprite.pyx @@ -0,0 +1,61 @@ +# -*- encoding: utf-8 -*- +## +## Copyright (C) 2011 Thibaut Girka +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## + + +from libc.math cimport M_PI as pi + + +cpdef object get_sprite_rendering_data(Sprite sprite): + cdef double x, y, tx, ty, tw, th, sx, sy, rz, tox, toy + + if not sprite.changed: + return sprite._rendering_data + + x = 0 + y = 0 + + tx, ty, tw, th = sprite.texcoords + sx, sy = sprite.rescale + width = sprite.width_override or (tw * sx) + height = sprite.height_override or (th * sy) + + rz = sprite.rotations_3d[2] + if sprite.automatic_orientation: + rz += pi/2. - sprite.angle + elif sprite.force_rotation: + rz += sprite.angle + + if sprite.allow_dest_offset: + x += sprite.dest_offset[0] + y += sprite.dest_offset[1] + if not sprite.corner_relative_placement: # Reposition + x -= width / 2 + y -= height / 2 + + size = sprite.anm.size + x_1 = 1 / size[0] + y_1 = 1 / size[1] + tox, toy = sprite.texoffsets + uvs = (tx * x_1 + tox, + (tx + tw) * x_1 + tox, + ty * y_1 + toy, + (ty + th) * y_1 + toy) + + key = sprite.blendfunc + r, g, b = sprite.color + values = (x, y, width, height), uvs, (r, g, b, sprite.alpha), -rz * 180 / pi, sprite.mirrored + sprite._rendering_data = key, values + sprite.changed = False + + return sprite._rendering_data diff --git a/pytouhou/ui/sdl/texture.pxd b/pytouhou/ui/sdl/texture.pxd new file mode 100644 --- /dev/null +++ b/pytouhou/ui/sdl/texture.pxd @@ -0,0 +1,15 @@ +#from pytouhou.lib.sdl cimport Font +from pytouhou.lib.sdl cimport Surface, Window + +cdef class TextureManager: + cdef object loader + cdef Window window + + cdef void load(self, dict anms) except * + cdef load_texture(self, Surface texture) + +#cdef class FontManager: +# cdef Font font +# cdef object renderer, texture_class +# +# cdef load(self, list labels) diff --git a/pytouhou/ui/sdl/texture.pyx b/pytouhou/ui/sdl/texture.pyx new file mode 100644 --- /dev/null +++ b/pytouhou/ui/sdl/texture.pyx @@ -0,0 +1,63 @@ +# -*- encoding: utf-8 -*- +## +## Copyright (C) 2011 Thibaut Girka +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## + +from pytouhou.lib.sdl cimport load_png, create_rgb_surface + +import os + + +cdef class TextureManager: + def __init__(self, loader, window): + self.loader = loader + self.window = window + + + cdef void load(self, dict anms): + for anm in sorted(anms.values(), key=is_ascii): + for entry in anm: + if not hasattr(entry, 'texture'): + texture = decode_png(self.loader, entry.first_name, entry.secondary_name) + #elif not isinstance(entry.texture, self.texture_class): + # texture = entry.texture + entry.texture = self.load_texture(texture) + anms.clear() + + + cdef load_texture(self, Surface surface): + return self.window.create_texture_from_surface(surface) + + +def is_ascii(anm): + return anm[0].first_name.endswith('ascii.png') + + +cdef Surface decode_png(loader, first_name, secondary_name): + image_file = load_png(loader.get_file(os.path.basename(first_name))) + width, height = image_file.surface.w, image_file.surface.h + + # Support only 32 bits RGBA. Paletted surfaces are awful to work with. + #TODO: verify it doesn’t blow up on big-endian systems. + new_image = create_rgb_surface(width, height, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000) + new_image.blit(image_file) + + if secondary_name: + alpha_file = load_png(loader.get_file(os.path.basename(secondary_name))) + assert (width == alpha_file.surface.w and height == alpha_file.surface.h) + + new_alpha_file = create_rgb_surface(width, height, 24) + new_alpha_file.blit(alpha_file) + + new_image.set_alpha(new_alpha_file) + + return new_image diff --git a/pytouhou/ui/sprite.pyx b/pytouhou/ui/sprite.pyx --- a/pytouhou/ui/sprite.pyx +++ b/pytouhou/ui/sprite.pyx @@ -20,7 +20,6 @@ from pytouhou.ui.renderer cimport Textur cpdef object get_sprite_rendering_data(Sprite sprite): - cdef Matrix vertmat cdef double tx, ty, tw, th, sx, sy, rx, ry, rz, tox, toy cdef object tmp1, tmp2 diff --git a/pytouhou/ui/window.pxd b/pytouhou/ui/window.pxd --- a/pytouhou/ui/window.pxd +++ b/pytouhou/ui/window.pxd @@ -1,5 +1,4 @@ from pytouhou.lib cimport sdl -from .gamerenderer cimport GameRenderer cdef class Clock: diff --git a/pytouhou/ui/window.pyx b/pytouhou/ui/window.pyx --- a/pytouhou/ui/window.pyx +++ b/pytouhou/ui/window.pyx @@ -102,8 +102,9 @@ cdef class Window: flags |= sdl.WINDOW_OPENGL - if not self.use_fixed_pipeline: - flags |= sdl.WINDOW_RESIZABLE + #TODO: implement it in the SDL backend too. + if not self.use_fixed_pipeline: + flags |= sdl.WINDOW_RESIZABLE self.win = sdl.Window('PyTouhou', sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED, @@ -126,6 +127,8 @@ cdef class Window: glEnableClientState(GL_COLOR_ARRAY) glEnableClientState(GL_VERTEX_ARRAY) glEnableClientState(GL_TEXTURE_COORD_ARRAY) + else: + self.win.create_renderer(0) self.clock = Clock(self.fps_limit) @@ -152,7 +155,7 @@ cdef class Window: cdef bint running = False if self.runner is not None: running = self.runner.update() - self.win.gl_swap_window() + self.win.present() self.clock.tick() return running diff --git a/setup.py b/setup.py --- a/setup.py +++ b/setup.py @@ -37,11 +37,14 @@ def get_arguments(arg, libraries): for directory, _, files in os.walk('pytouhou'): package = directory.replace(os.path.sep, '.') packages.append(package) - if package not in ('pytouhou.game', 'pytouhou.lib', 'pytouhou.ui', 'pytouhou.utils'): + if package not in ('pytouhou.game', 'pytouhou.lib', 'pytouhou.ui', 'pytouhou.utils', 'pytouhou.ui.sdl'): continue if package == 'pytouhou.ui': compile_args = get_arguments('--cflags', ['gl'] + SDL_LIBRARIES) link_args = get_arguments('--libs', ['gl'] + SDL_LIBRARIES) + elif package == 'pytouhou.ui.sdl': + compile_args = get_arguments('--cflags', SDL_LIBRARIES) + link_args = get_arguments('--libs', SDL_LIBRARIES) else: compile_args = None link_args = None