view pytouhou/lib/sdl.pyx @ 612:73f134f84c7f

Request a RGB888 context, since SDL2’s default of RGB332 sucks. On X11/GLX, it will select the first config available, that is the best one, while on EGL it will iterate over them to select the one closest to what the application requested. Of course, anything lower than RGB888 looks bad and we really don’t want that.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Thu, 26 Mar 2015 20:20:37 +0100
parents 3c2f96f1d715
children d1f0bb0b7a17
line wrap: on
line source

# -*- encoding: utf-8 -*-
##
## Copyright (C) 2013 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
##
## 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.utils.helpers import get_logger

logger = get_logger(__name__)


GL_CONTEXT_MAJOR_VERSION = SDL_GL_CONTEXT_MAJOR_VERSION
GL_CONTEXT_MINOR_VERSION = SDL_GL_CONTEXT_MINOR_VERSION
GL_CONTEXT_PROFILE_MASK = SDL_GL_CONTEXT_PROFILE_MASK
GL_DOUBLEBUFFER = SDL_GL_DOUBLEBUFFER
GL_RED_SIZE = SDL_GL_RED_SIZE
GL_GREEN_SIZE = SDL_GL_GREEN_SIZE
GL_BLUE_SIZE = SDL_GL_BLUE_SIZE
GL_DEPTH_SIZE = SDL_GL_DEPTH_SIZE

GL_CONTEXT_PROFILE_CORE = SDL_GL_CONTEXT_PROFILE_CORE
GL_CONTEXT_PROFILE_COMPATIBILITY = SDL_GL_CONTEXT_PROFILE_COMPATIBILITY
GL_CONTEXT_PROFILE_ES = SDL_GL_CONTEXT_PROFILE_ES

WINDOWPOS_CENTERED = SDL_WINDOWPOS_CENTERED
WINDOW_OPENGL = SDL_WINDOW_OPENGL
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
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

KEYDOWN = SDL_KEYDOWN
QUIT = SDL_QUIT
WINDOWEVENT = SDL_WINDOWEVENT


class SDLError(Exception):
    def __init__(self):
        error = SDL_GetError()
        Exception.__init__(self, error.decode())


class SDL(object):
    def __init__(self, sound=True):
        self.sound = sound

    def __enter__(self):
        global keyboard_state

        IF UNAME_SYSNAME == "Windows":
            SDL_SetMainReady()
        init(SDL_INIT_VIDEO)
        img_init(IMG_INIT_PNG)
        ttf_init()

        keyboard_state = SDL_GetKeyboardState(NULL)

        if self.sound:
            mix_init(0)
            try:
                mix_open_audio(44100, MIX_DEFAULT_FORMAT, 2, 4096)
            except SDLError as error:
                logger.error(u'Impossible to set up audio subsystem: %s', error)
                self.sound = False
            else:
                # TODO: make it dependent on the number of sound files in the
                # archives.
                mix_allocate_channels(MAX_SOUNDS)

    def __exit__(self, *args):
        if self.sound:
            Mix_CloseAudio()
            Mix_Quit()

        TTF_Quit()
        IMG_Quit()
        SDL_Quit()


cdef class Window:
    def __init__(self, str title, int x, int y, int w, int h, Uint32 flags):
        title_bytes = title.encode()
        self.window = SDL_CreateWindow(title_bytes, x, y, w, h, flags)
        if self.window == NULL:
            raise SDLError()

    def __dealloc__(self):
        if self.context != NULL:
            SDL_GL_DeleteContext(self.context)
        if self.window != NULL:
            SDL_DestroyWindow(self.window)

    cdef void gl_create_context(self) except *:
        self.context = SDL_GL_CreateContext(self.window)
        if self.context == NULL:
            raise SDLError()

    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()

    cdef void render_clear(self):
        ret = SDL_RenderClear(self.renderer)
        if ret == -1:
            raise SDLError()

    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()

    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()

    cdef void render_set_clip_rect(self, Rect rect):
        ret = SDL_RenderSetClipRect(self.renderer, &rect.rect)
        if ret == -1:
            raise SDLError()

    cdef void render_set_viewport(self, Rect rect):
        ret = SDL_RenderSetViewport(self.renderer, &rect.rect)
        if ret == -1:
            raise SDLError()

    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()
        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()

    cpdef set_alpha_mod(self, Uint8 alpha):
        ret = SDL_SetTextureAlphaMod(self.texture, alpha)
        if ret == -1:
            raise SDLError()

    cpdef set_blend_mode(self, SDL_BlendMode blend_mode):
        ret = SDL_SetTextureBlendMode(self.texture, blend_mode)
        if ret == -1:
            raise SDLError()


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):
        if self.surface != NULL:
            SDL_FreeSurface(self.surface)

    property pixels:
        def __get__(self):
            return bytes(self.surface.pixels[:self.surface.w * self.surface.h * 4])

    cdef void blit(self, Surface other) except *:
        if SDL_BlitSurface(other.surface, NULL, self.surface, NULL) < 0:
            raise SDLError()

    cdef void set_alpha(self, Surface alpha_surface) nogil:
        nb_pixels = self.surface.w * self.surface.h
        image = self.surface.pixels
        alpha = alpha_surface.surface.pixels

        for i in range(nb_pixels):
            # Only use the red value, assume the others are equal.
            image[3+4*i] = alpha[3*i]


cdef class Music:
    def __dealloc__(self):
        if self.music != NULL:
            Mix_FreeMusic(self.music)

    cdef void play(self, int loops) nogil:
        Mix_PlayMusic(self.music, loops)

    cdef void set_loop_points(self, double start, double end) nogil:
        #Mix_SetLoopPoints(self.music, start, end)
        pass


cdef class Chunk:
    def __dealloc__(self):
        if self.chunk != NULL:
            Mix_FreeChunk(self.chunk)

    cdef void play(self, int channel, int loops) nogil:
        Mix_PlayChannel(channel, self.chunk, loops)

    cdef void set_volume(self, float volume) nogil:
        Mix_VolumeChunk(self.chunk, int(volume * 128))


cdef class Font:
    def __init__(self, str filename, int ptsize):
        path = filename.encode()
        self.font = TTF_OpenFont(path, ptsize)
        if self.font == NULL:
            raise SDLError()

    def __dealloc__(self):
        if self.font != NULL:
            TTF_CloseFont(self.font)

    cdef Surface render(self, unicode text):
        cdef SDL_Color white
        white = SDL_Color(255, 255, 255, 255)
        surface = Surface()
        string = text.encode('utf-8')
        surface.surface = TTF_RenderUTF8_Blended(self.font, string, white)
        if surface.surface == NULL:
            raise SDLError()
        return surface


cdef void init(Uint32 flags) except *:
    if SDL_Init(flags) < 0:
        raise SDLError()


cdef void img_init(int flags) except *:
    if IMG_Init(flags) != flags:
        raise SDLError()


cdef void mix_init(int flags) except *:
    if Mix_Init(flags) != flags:
        raise SDLError()


cdef void ttf_init() except *:
    if TTF_Init() < 0:
        raise SDLError()


cdef void gl_set_attribute(SDL_GLattr attr, int value) except *:
    if SDL_GL_SetAttribute(attr, value) < 0:
        raise SDLError()

cdef int gl_set_swap_interval(int interval) except *:
    if SDL_GL_SetSwapInterval(interval) < 0:
        raise SDLError()


cdef list poll_events():
    cdef SDL_Event event
    ret = []
    while SDL_PollEvent(&event):
        if event.type == SDL_KEYDOWN:
            ret.append((event.type, event.key.keysym.scancode))
        elif event.type == SDL_QUIT:
            ret.append((event.type,))
        elif event.type == SDL_WINDOWEVENT:
            ret.append((event.type, event.window.event, event.window.data1, event.window.data2))
    return ret


cdef Surface load_png(file_):
    data = file_.read()
    rwops = SDL_RWFromConstMem(<char*>data, len(data))
    surface = Surface()
    surface.surface = IMG_LoadPNG_RW(rwops)
    SDL_RWclose(rwops)
    if surface.surface == NULL:
        raise SDLError()
    return surface


cdef Surface create_rgb_surface(int width, int height, int depth, Uint32 rmask=0, Uint32 gmask=0, Uint32 bmask=0, Uint32 amask=0):
    surface = Surface()
    surface.surface = SDL_CreateRGBSurface(0, width, height, depth, rmask, gmask, bmask, amask)
    if surface.surface == NULL:
        raise SDLError()
    return surface


cdef void mix_open_audio(int frequency, Uint16 format_, int channels, int chunksize) except *:
    if Mix_OpenAudio(frequency, format_, channels, chunksize) < 0:
        raise SDLError()


cdef void mix_allocate_channels(int numchans) except *:
    if Mix_AllocateChannels(numchans) != numchans:
        raise SDLError()


cdef int mix_volume(int channel, float volume) nogil:
    return Mix_Volume(channel, int(volume * 128))


cdef int mix_volume_music(float volume) nogil:
    return Mix_VolumeMusic(int(volume * 128))


cdef Music load_music(str filename):
    music = Music()
    path = filename.encode()
    music.music = Mix_LoadMUS(path)
    if music.music == NULL:
        raise SDLError()
    return music


cdef Chunk load_chunk(file_):
    cdef SDL_RWops *rwops
    chunk = Chunk()
    data = file_.read()
    rwops = SDL_RWFromConstMem(<char*>data, len(data))
    chunk.chunk = Mix_LoadWAV_RW(rwops, 1)
    if chunk.chunk == NULL:
        raise SDLError()
    return chunk


cdef Uint32 get_ticks() nogil:
    return SDL_GetTicks()


cdef void delay(Uint32 ms) nogil:
    SDL_Delay(ms)


cpdef int show_simple_message_box(unicode message):
    text = message.encode('UTF-8')
    return SDL_ShowSimpleMessageBox(1, 'PyTouhou', text, NULL)