# HG changeset patch # User Emmanuel Gil Peyrot # Date 1662393216 -7200 # Node ID 7492d384d122d7b189759f77fdbfb1482bfdbd0e # Parent 79c3f782dd4109b827dc49d111406f11719c0513 Rust: Add a Glide renderer (2D only for now) This is an experiment for a Rust renderer, iterating over the Python data using pyo3. It requires --feature=glide to be passed to cargo build, doesn’t support NPOT textures, text rendering, the background, or even msg faces, some of that may come in a future changeset. diff --git a/python/Cargo.toml b/python/Cargo.toml --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -15,3 +15,8 @@ name = "touhou" [dependencies] touhou-formats = "*" pyo3 = "0.17" +image = { version = "0.24", default-features = false, features = ["png"], optional = true } + +[features] +default = [] +glide = ["image"] diff --git a/python/src/glide/gr.rs b/python/src/glide/gr.rs new file mode 100644 --- /dev/null +++ b/python/src/glide/gr.rs @@ -0,0 +1,312 @@ +use core::ptr::null; + +#[link(name = "glide2x")] +extern "C" { + fn grGlideInit(); + fn grGlideShutdown(); + fn grSstSelect(sst: u32); + fn grSstWinOpen(hwnd: u32, resolution: u32, refresh: u32, color_format: u32, origin_location: u32, num_buf: i32, num_aux_buf: i32); + fn grBufferSwap(interval: i32); + fn grBufferClear(color: u32, alpha: u8, depth: u16); + fn grTexCalcMemRequired(min: Lod, max: Lod, aspect: AspectRatio, format: TextureFormat) -> u32; + fn grTexDownloadMipMap(tmu: u32, start: u32, even_odd: EvenOdd, info: *const TexInfo); + fn grTexSource(tmu: u32, start: u32, even_odd: EvenOdd, info: &TexInfo); + fn grTexMinAddress(tmu: u32) -> u32; + fn grTexMaxAddress(tmu: u32) -> u32; + fn guAlphaSource(mode: AlphaSource) -> u32; + fn guColorCombineFunction(function: ColorCombineFnc) -> u32; + fn grTexCombineFunction(tmu: u32, function: TextureCombineFnc) -> u32; + fn grDrawTriangle(a: *const Vertex, b: *const Vertex, c: *const Vertex); + fn grAlphaBlendFunction(a: Blend, b: Blend, c: Blend, d: Blend); +} + +#[repr(i32)] +#[derive(Clone, Copy)] +pub enum Lod { + L256x256 = 0, + L128x128 = 1, + L64x64 = 2, + L32x32 = 3, + L16x16 = 4, + L8x8 = 5, + L4x4 = 6, + L2x2 = 7, + L1x1 = 8, +} + +#[repr(i32)] +#[derive(Clone, Copy)] +pub enum AspectRatio { + A8x1 = 0, + A4x1 = 1, + A2x1 = 2, + A1x1 = 3, + A1x2 = 4, + A1x4 = 5, + A1x8 = 6, +} + +fn lod_aspect_from_dimensions(dimensions: (u32, u32)) -> (Lod, AspectRatio) { + match dimensions { + (256, 256) => (Lod::L256x256, AspectRatio::A1x1), + (128, 128) => (Lod::L128x128, AspectRatio::A1x1), + (64, 64) => (Lod::L64x64, AspectRatio::A1x1), + (32, 32) => (Lod::L32x32, AspectRatio::A1x1), + (16, 16) => (Lod::L16x16, AspectRatio::A1x1), + (8, 8) => (Lod::L8x8, AspectRatio::A1x1), + (4, 4) => (Lod::L4x4, AspectRatio::A1x1), + (2, 2) => (Lod::L2x2, AspectRatio::A1x1), + (1, 1) => (Lod::L1x1, AspectRatio::A1x1), + (width, height) => todo!("NPOT texture size {width}×{height}"), + } +} + +#[repr(i32)] +#[derive(Clone, Copy, Debug)] +pub enum TextureFormat { + Rgb332 = 0, + Yiq422 = 1, + Alpha8 = 2, + Intensity8 = 3, + AlphaIntensity44 = 4, + P8 = 5, + Argb8332 = 8, + Ayiq8422 = 9, + Rgb565 = 10, + Argb1555 = 11, + Argb4444 = 12, + AlphaIntensity88 = 13, + Ap88 = 14, +} + +#[repr(C)] +pub struct TexInfo { + pub small_lod: Lod, + pub large_lod: Lod, + pub aspect: AspectRatio, + pub format: TextureFormat, + data: *const u8, +} + +impl TexInfo { + pub fn new(width: u32, height: u32, format: TextureFormat) -> TexInfo { + let (lod, aspect) = lod_aspect_from_dimensions((width, height)); + TexInfo { + small_lod: lod, + large_lod: lod, + aspect, + format, + data: null(), + } + } + + pub fn with_data(width: u32, height: u32, format: TextureFormat, data: &[u8]) -> TexInfo { + let (lod, aspect) = lod_aspect_from_dimensions((width, height)); + TexInfo { + small_lod: lod, + large_lod: lod, + aspect, + format, + data: data.as_ptr(), + } + } +} + +#[repr(C)] +pub struct Vertex { + x: f32, + y: f32, + z: f32, + r: f32, + g: f32, + b: f32, + ooz: f32, + a: f32, + oow: f32, + sow0: f32, + tow0: f32, + oow0: f32, + sow1: f32, + tow1: f32, + oow1: f32, +} + +impl Vertex { + pub fn new(x: f32, y: f32, sow: f32, tow: f32, color: u32) -> Vertex { + let z = 1.0; + let r = ((color >> 24) & 0xff) as f32; + let g = ((color >> 16) & 0xff) as f32; + let b = ((color >> 8) & 0xff) as f32; + let a = (color & 0xff) as f32; + let ooz = 1.0; + let oow = 1.0; + let sow0 = sow; + let tow0 = tow; + let oow0 = 1.0; + let sow1 = sow; + let tow1 = tow; + let oow1 = 1.0; + Vertex { + x, y, z, + r, g, b, + ooz, + a, + oow, + sow0, tow0, oow0, + sow1, tow1, oow1, + } + } +} + +pub fn glide_init() { + unsafe { grGlideInit() }; +} + +pub fn glide_shutdown() { + unsafe { grGlideShutdown() }; +} + +pub fn sst_select(sst: u32) { + unsafe { grSstSelect(sst) }; +} + +pub fn sst_win_open(width: u32, height: u32, refresh: u32) { + let resolution = match (width, height) { + (320, 200) => 0, + (320, 240) => 1, + (400, 256) => 2, + (512, 384) => 3, + (640, 200) => 4, + (640, 350) => 5, + (640, 400) => 6, + (640, 480) => 7, + _ => unreachable!("Unknown screen resolution {width}×{height}."), + }; + let refresh = match refresh { + 60 => 0, + 70 => 1, + 72 => 2, + 75 => 3, + 80 => 4, + 90 => 5, + 100 => 6, + 85 => 7, + 120 => 8, + _ => unreachable!("Unknown refresh rate {refresh} Hz."), + }; + let color_format = 2; // RGBA + let origin_location = 0; // Upper Left + unsafe { grSstWinOpen(0, resolution, refresh, color_format, origin_location, 2, 0) }; +} + +pub fn buffer_swap(interval: i32) { + unsafe { grBufferSwap(interval) }; +} + +pub fn buffer_clear(color: u32, alpha: u8, depth: u16) { + unsafe { grBufferClear(color, alpha, depth) }; +} + +pub fn tex_calc_mem_required(small_lod: Lod, large_lod: Lod, aspect: AspectRatio, format: TextureFormat) -> u32 { + unsafe { grTexCalcMemRequired(small_lod, large_lod, aspect, format) } +} + +pub fn tex_download_mip_map(tmu: u32, start: u32, even_odd: EvenOdd, info: &TexInfo) { + unsafe { grTexDownloadMipMap(tmu, start, even_odd, info) }; +} + +pub fn tex_source(tmu: u32, start: u32, even_odd: EvenOdd, info: &TexInfo) { + unsafe { grTexSource(tmu, start, even_odd, info) }; +} + +pub fn tex_min_address(tmu: u32) -> u32 { + unsafe { grTexMinAddress(tmu) } +} + +pub fn tex_max_address(tmu: u32) -> u32 { + unsafe { grTexMaxAddress(tmu) } +} + +pub fn alpha_source(mode: AlphaSource) { + unsafe { guAlphaSource(mode) }; +} + +pub fn color_combine_function(function: ColorCombineFnc) { + unsafe { guColorCombineFunction(function) }; +} + +pub fn tex_combine_function(tmu: u32, function: TextureCombineFnc) { + unsafe { grTexCombineFunction(tmu, function) }; +} + +pub fn alpha_blend_function(a: Blend, b: Blend, c: Blend, d: Blend) { + unsafe { grAlphaBlendFunction(a, b, c, d) }; +} + +#[repr(i32)] +pub enum EvenOdd { + Even = 0, + Odd = 1, + Both = 2, +} + +#[repr(i32)] +pub enum Blend { + Zero = 0, + SrcAlpha = 1, + SrcColor = 2, + DstAlpha = 3, + One = 4, + OneMinusSrcAlpha = 5, + OneMinusSrcColor = 6, + OneMinusDstAlpha = 7, + AlphaSaturate = 15, +} + +#[repr(i32)] +pub enum AlphaSource { + CcAlpha = 0, + IteratedAlpha = 1, + TextureAlpha = 2, + TextureAlphaTimesIteratedAlpha = 3, +} + +#[repr(i32)] +pub enum ColorCombineFnc { + Zero = 0, + Ccrgb = 1, + Itrgb = 2, + ItrgbDelta0 = 3, + DecalTexture = 4, + TextureTimesCcrgb = 5, + TextureTimesItrgb = 6, + TextureTimesItrgbDelta0 = 7, + TextureTimesItrgbAddAlpha = 8, + TextureTimesAlpha = 9, + TextureTimesAlphaAddItrgb = 10, + TextureAddItrgb = 11, + TextureSubItrgb = 12, + CcrgbBlendItrgbOnTexalpha = 13, + DiffSpecA = 14, + DiffSpecB = 15, + One = 16, +} + +#[repr(i32)] +pub enum TextureCombineFnc { + Zero = 0, + Decal = 1, + Other = 2, + Add = 3, + Multiply = 4, + Subtract = 5, + Detail = 6, + DetailOther = 7, + TrilinearOdd = 8, + TrilinearEven = 9, + One = 10, +} + +pub fn draw_triangle(a: &Vertex, b: &Vertex, c: &Vertex) { + unsafe { grDrawTriangle(a, b, c) }; +} diff --git a/python/src/glide/mod.rs b/python/src/glide/mod.rs new file mode 100644 --- /dev/null +++ b/python/src/glide/mod.rs @@ -0,0 +1,258 @@ +use pyo3::prelude::*; +use pyo3::types::{PyList, PyDict, PySequence}; +use pyo3::exceptions::PyTypeError; +use std::collections::{HashMap, BTreeMap}; +use image::GenericImageView; + +mod gr; + +#[inline(always)] +fn pixel_to_rgb332(pixel: [u8; 4]) -> [u8; 1] { + [(pixel[0] & 0xe0) | ((pixel[1] >> 3) & 0x1c) | (pixel[2] >> 6)] +} + +#[inline(always)] +fn pixel_to_argb8332(pixel: [u8; 4]) -> [u8; 2] { + [(pixel[0] & 0xe0) | ((pixel[1] >> 3) & 0x1c) | (pixel[2] >> 6), pixel[3]] +} + +#[inline(always)] +fn pixel_to_argb4444(pixel: [u8; 4]) -> [u8; 2] { + [(pixel[1] & 0xf0) | (pixel[2] >> 4), (pixel[3] & 0xf0) | (pixel[0] >> 4)] +} + +#[inline(always)] +fn pixel_to_argb1555(pixel: [u8; 4]) -> [u8; 2] { + [((pixel[1] << 2) & 0xe0) | (pixel[2] >> 3), (pixel[3] & 0x80) | ((pixel[0] >> 1) & 0x7c) | (pixel[1] >> 6)] +} + +#[inline(always)] +fn pixel_to_rgb565(pixel: [u8; 4]) -> [u8; 2] { + [((pixel[1] << 3) & 0xe0) | (pixel[2] >> 3), (pixel[0] & 0xf8) | (pixel[1] >> 5)] +} + +fn merge_alpha(rgb: &image::DynamicImage, alpha: &image::DynamicImage) -> Vec { + let alpha = match alpha.grayscale() { + image::DynamicImage::ImageLuma8(img) => img, + foo => panic!("TODO {:?} {:?}", alpha, foo), + }; + rgb + .pixels() + .zip(alpha.pixels()) + .map(|((_x, _y, rgb), alpha)| pixel_to_argb4444([rgb[0], rgb[1], rgb[2], alpha[0]])) + .flatten() + .collect::>() +} + +#[derive(Debug)] +struct TextureManager { + tmu: u32, + next_tex_location: u32, + max_tex_location: u32, + textures: BTreeMap, +} + +impl TextureManager { + fn new(tmu: u32) -> TextureManager { + let next_tex_location = gr::tex_min_address(tmu); + let max_tex_location = gr::tex_max_address(tmu); + let textures = BTreeMap::new(); + TextureManager { + tmu, + next_tex_location, + max_tex_location, + textures, + } + } + + fn download(&mut self, tex: &gr::TexInfo) -> PyResult { + let location = self.next_tex_location; + let size = gr::tex_calc_mem_required(tex.small_lod, tex.large_lod, tex.aspect, tex.format); + if location + size > self.max_tex_location { + return Err(PyTypeError::new_err("Out of memory")); + } + gr::tex_download_mip_map(self.tmu, location, gr::EvenOdd::Both, tex); + self.next_tex_location += size; + self.textures.insert(location, tex.format); + Ok(location) + } + + fn get(&self, address: u32) -> gr::TexInfo { + if let Some(&format) = self.textures.get(&address) { + gr::TexInfo::new(256, 256, format) + } else { + unreachable!("Not uploaded texture at address 0x{:08x}!", address); + } + } +} + +#[pyclass] +struct GameRenderer { + #[pyo3(get, set)] + size: (u32, u32, u32, u32), + + texture_manager: TextureManager, +} + +#[pymethods] +impl GameRenderer { + #[new] + fn new() -> GameRenderer { + let size = (0, 0, 0, 0); + let texture_manager = TextureManager::new(0); + GameRenderer { + size, + texture_manager, + } + } + + fn start(&self, common: PyObject) { + gr::color_combine_function(gr::ColorCombineFnc::TextureTimesItrgb); + gr::alpha_blend_function(gr::Blend::SrcAlpha, gr::Blend::OneMinusSrcAlpha, gr::Blend::One, gr::Blend::Zero); + gr::alpha_source(gr::AlphaSource::TextureAlphaTimesIteratedAlpha); + gr::tex_combine_function(0, gr::TextureCombineFnc::Decal); + } + + fn load_textures(&mut self, py: Python, anms: HashMap>) -> PyResult<()> { + for (filename, anm) in anms { + for anm in anm { + let png_rgb: String = anm.getattr(py, "first_name")?.extract(py)?; + let png_alpha: Option = anm.getattr(py, "secondary_name")?.extract(py)?; + let (_, png_rgb) = png_rgb.rsplit_once('/').unwrap(); + use std::path::PathBuf; + let texture_address = if let Some(png_alpha) = png_alpha { + let (_, png_alpha) = png_alpha.rsplit_once('/').unwrap(); + //image::load_from_memory_with_format(b"", image::ImageFormat::Png).unwrap(); + let rgb = image::open(["/", "tmp", "touhou", png_rgb].iter().collect::()).unwrap(); + let alpha = image::open(["/", "tmp", "touhou", png_alpha].iter().collect::()).unwrap(); + assert_eq!(rgb.dimensions(), alpha.dimensions()); + let (width, height) = rgb.dimensions(); + let rgba = merge_alpha(&rgb, &alpha); + let tex = gr::TexInfo::with_data(width, height, gr::TextureFormat::Argb4444, &rgba); + self.texture_manager.download(&tex)? + } else { + //image::load_from_memory_with_format(b"", image::ImageFormat::Png).unwrap(); + let rgb = image::open(["/", "tmp", "touhou", png_rgb].iter().collect::()).unwrap(); + let (width, height) = rgb.dimensions(); + let rgb = rgb.pixels() + .map(|(x, y, rgb)| pixel_to_rgb565([rgb[0], rgb[1], rgb[2], 0xff])) + .flatten() + .collect::>(); + let tex = gr::TexInfo::with_data(width, height, gr::TextureFormat::Rgb565, &rgb); + self.texture_manager.download(&tex)? + }; + anm.setattr(py, "texture", texture_address)?; + let texture: u32 = anm.getattr(py, "texture")?.extract(py)?; + } + } + Ok(()) + } + + fn load_background(&self, background: PyObject) { + println!("TODO: GameRenderer::load_background({background})"); + } + + fn render_elements(&self, py: Python, elements: &PyList, shift: (f32, f32)) -> PyResult<()> { + let module = py.import("pytouhou.ui.glide.sprite")?; + let get_sprite_rendering_data = module.getattr("get_sprite_rendering_data")?; + let mut prev_texture = u32::MAX; + for element in elements.iter() { + /* + // TODO: only for enemies. + let visible: bool = element.getattr("visible")?.extract()?; + if !visible { + continue; + } + */ + let x: f32 = element.getattr("x")?.extract()?; + let y: f32 = element.getattr("y")?.extract()?; + let sprite = element.getattr("sprite")?; + if !sprite.is_none() { + let (pos, mut texcoords, color): ([f32; 12], [f32; 4], u32) = get_sprite_rendering_data.call1((sprite,))?.extract()?; + for coord in &mut texcoords { + *coord *= 256.0; + } + let anm = sprite.getattr("anm")?; + let texture = anm.getattr("texture")?.extract()?; + if texture != prev_texture { + let tex = self.texture_manager.get(texture); + gr::tex_source(0, texture, gr::EvenOdd::Both, &tex); + prev_texture = texture; + } + draw_triangle(x + shift.0, y + shift.1, pos, texcoords, color); + } + } + Ok(()) + } + + fn render(&self, py: Python, game: PyObject) -> PyResult<()> { + gr::buffer_clear(0x000000ff, 0xff, 0xffff); + for things in ["enemies", "effects", "players_bullets"/*, "lasers_sprites()"*/, "players"/*, "msg_sprites()"*/, "bullets", "lasers", "cancelled_bullets", "items", "labels"] { + let things = game.getattr(py, things)?; + let things: &PyList = things.extract(py)?; + self.render_elements(py, things, (32.0, 16.0))?; + } + let interface = game.getattr(py, "interface")?; + let boss = game.getattr(py, "boss")?; + self.render_interface(py, interface, !boss.is_none(py))?; + Ok(()) + } + + fn render_interface(&self, py: Python, interface: PyObject, boss: bool) -> PyResult<()> { + let items = interface.getattr(py, "items")?; + let items: &PyList = items.extract(py)?; + self.render_elements(py, items, (0.0, 0.0))?; + /* + // TODO: figure out why this doesn’t render alphanumeric characters. + let labels = interface.getattr(py, "labels")?; + let labels: &PyDict = labels.extract(py)?; + self.render_elements(py, labels.values(), (0.0, 0.0))?; + */ + if boss { + let items = interface.getattr(py, "boss_items")?; + let items: &PyList = items.extract(py)?; + self.render_elements(py, items, (0.0, 0.0))?; + } + Ok(()) + } +} + +fn draw_triangle(ox: f32, oy: f32, pos: [f32; 12], texcoords: [f32; 4], color: u32) { + let a = gr::Vertex::new(ox + pos[0], oy + pos[4], texcoords[0], texcoords[2], color); + let b = gr::Vertex::new(ox + pos[1], oy + pos[5], texcoords[1], texcoords[2], color); + let c = gr::Vertex::new(ox + pos[2], oy + pos[6], texcoords[1], texcoords[3], color); + let d = gr::Vertex::new(ox + pos[3], oy + pos[7], texcoords[0], texcoords[3], color); + gr::draw_triangle(&a, &b, &c); + gr::draw_triangle(&a, &c, &d); +} + +#[pyfunction] +fn init(options: HashMap) { + gr::glide_init(); + gr::sst_select(0); +} + +#[pyfunction] +fn shutdown() { + gr::glide_shutdown(); +} + +#[pyfunction] +fn create_window(title: &str, posx: u32, posy: u32, width: u32, height: u32, frameskip: u32) { + gr::sst_win_open(640, 480, 60); +} + +#[pyfunction] +fn buffer_swap() { + gr::buffer_swap(1); +} + +pub fn module(py: Python) -> PyResult<&PyModule> { + let m = PyModule::new(py, "glide")?; + m.add_class::()?; + m.add_function(wrap_pyfunction!(init, m)?)?; + m.add_function(wrap_pyfunction!(shutdown, m)?)?; + m.add_function(wrap_pyfunction!(create_window, m)?)?; + m.add_function(wrap_pyfunction!(buffer_swap, m)?)?; + Ok(&m) +} diff --git a/python/src/lib.rs b/python/src/lib.rs --- a/python/src/lib.rs +++ b/python/src/lib.rs @@ -4,6 +4,9 @@ use touhou_formats::th06::pbg3; use std::fs::File; use std::io::BufReader; +#[cfg(feature = "glide")] +mod glide; + #[pyclass] struct PBG3 { inner: pbg3::PBG3>, @@ -35,7 +38,9 @@ impl PBG3 { } #[pymodule] -fn libtouhou(_py: Python, m: &PyModule) -> PyResult<()> { +fn libtouhou(py: Python, m: &PyModule) -> PyResult<()> { m.add_class::()?; + #[cfg(feature = "glide")] + m.add_submodule(glide::module(py)?)?; Ok(()) } diff --git a/pytouhou/options.py b/pytouhou/options.py --- a/pytouhou/options.py +++ b/pytouhou/options.py @@ -141,7 +141,7 @@ def parse_arguments(defaults): graphics_group = parser.add_argument_group('Graphics options') graphics_group.add_argument('--frontend', metavar='FRONTEND', choices=['glfw', 'sdl'], help='Which windowing library to use (glfw or sdl).') - graphics_group.add_argument('--backend', metavar='BACKEND', choices=['opengl', 'sdl'], nargs='*', help='Which backend to use (opengl or sdl).') + graphics_group.add_argument('--backend', metavar='BACKEND', choices=['opengl', 'glide', 'sdl'], nargs='*', help='Which backend to use (opengl, glide or sdl).') graphics_group.add_argument('--fps-limit', metavar='FPS', type=int, help='Set fps limit. A value of 0 disables fps limiting, while a negative value limits to 60 fps if and only if vsync doesn’t work.') graphics_group.add_argument('--frameskip', metavar='FRAMESKIP', type=int, help='Set the frameskip, as 1/FRAMESKIP, or disabled if 0.') graphics_group.add_argument('--no-background', action='store_false', help='Disable background display (huge performance boost on slow systems).') diff --git a/pytouhou/ui/glide/__init__.py b/pytouhou/ui/glide/__init__.py new file mode 100644 diff --git a/pytouhou/ui/glide/backend.py b/pytouhou/ui/glide/backend.py new file mode 100644 --- /dev/null +++ b/pytouhou/ui/glide/backend.py @@ -0,0 +1,9 @@ +from libtouhou import glide +from .window import Window + +def create_window(title, posx, posy, width, height, frameskip): + glide.create_window(title, posx, posy, width, height, frameskip) + return Window() + +init = glide.init +GameRenderer = glide.GameRenderer diff --git a/pytouhou/ui/glide/sprite.pxd b/pytouhou/ui/glide/sprite.pxd new file mode 100644 --- /dev/null +++ b/pytouhou/ui/glide/sprite.pxd @@ -0,0 +1,8 @@ +from pytouhou.game.sprite cimport Sprite + +cdef struct RenderingData: + float pos[12] + float left, right, bottom, top + unsigned char color[4] + +cdef void render_sprite(Sprite sprite) nogil diff --git a/pytouhou/ui/glide/sprite.pyx b/pytouhou/ui/glide/sprite.pyx new file mode 100644 --- /dev/null +++ b/pytouhou/ui/glide/sprite.pyx @@ -0,0 +1,86 @@ +# -*- 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.stdlib cimport malloc +from libc.string cimport memcpy +from libc.math cimport M_PI as pi + +from pytouhou.utils.matrix cimport Matrix, scale2d, flip, rotate_x, rotate_y, rotate_z, translate, translate2d + + +cdef Matrix default +default = Matrix(-.5, .5, .5, -.5, + -.5, -.5, .5, .5, + 0, 0, 0, 0, + 1, 1, 1, 1) + + +def get_sprite_rendering_data(Sprite sprite): + if sprite.changed: + render_sprite(sprite) + data = sprite._rendering_data + color = data.color[0] << 24 | data.color[1] << 16 | data.color[2] << 8 | data.color[3] + return (data.pos, [data.left, data.right, data.bottom, data.top], color) + + +cdef void render_sprite(Sprite sprite) nogil: + cdef Matrix vertmat + + if sprite._rendering_data == NULL: + sprite._rendering_data = malloc(sizeof(RenderingData)) + + data = sprite._rendering_data + memcpy(&vertmat, &default, sizeof(Matrix)) + + tx, ty, tw, th = sprite._texcoords[0], sprite._texcoords[1], sprite._texcoords[2], sprite._texcoords[3] + sx, sy = sprite._rescale[0], sprite._rescale[1] + width = sprite.width_override or (tw * sx) + height = sprite.height_override or (th * sy) + + scale2d(&vertmat, width, height) + if sprite.mirrored: + flip(&vertmat) + + rx, ry, rz = sprite._rotations_3d[0], sprite._rotations_3d[1], sprite._rotations_3d[2] + if sprite.automatic_orientation: + rz += pi/2. - sprite.angle + elif sprite.force_rotation: + rz += sprite.angle + + if rx: + rotate_x(&vertmat, -rx) + if ry: + rotate_y(&vertmat, ry) + if rz: + rotate_z(&vertmat, -rz) #TODO: minus, really? + if sprite.allow_dest_offset: + translate(&vertmat, sprite._dest_offset) + if sprite.corner_relative_placement: # Reposition + translate2d(&vertmat, width / 2, height / 2) + + memcpy(data.pos, &vertmat, 12 * sizeof(float)) + + x_1 = sprite.anm.size_inv[0] + y_1 = sprite.anm.size_inv[1] + tox, toy = sprite._texoffsets[0], sprite._texoffsets[1] + data.left = tx * x_1 + tox + data.right = (tx + tw) * x_1 + tox + data.bottom = ty * y_1 + toy + data.top = (ty + th) * y_1 + toy + + for i in range(4): + data.color[i] = sprite._color[i] + + sprite.changed = False diff --git a/pytouhou/ui/glide/window.pxd b/pytouhou/ui/glide/window.pxd new file mode 100644 --- /dev/null +++ b/pytouhou/ui/glide/window.pxd @@ -0,0 +1,9 @@ +cimport pytouhou.lib.gui as gui + +cdef class Window(gui.Window): + cdef void present(self) nogil + cdef void set_window_size(self, int width, int height) nogil + cdef void set_swap_interval(self, int interval) except * + cdef list get_events(self) + cdef int get_keystate(self) nogil + cdef void toggle_fullscreen(self) nogil diff --git a/pytouhou/ui/glide/window.pyx b/pytouhou/ui/glide/window.pyx new file mode 100644 --- /dev/null +++ b/pytouhou/ui/glide/window.pyx @@ -0,0 +1,22 @@ +import pytouhou.lib.gui as gui +import libtouhou + +cdef class Window(gui.Window): + cdef void present(self) nogil: + with gil: + libtouhou.glide.buffer_swap() + + cdef void set_window_size(self, int width, int height) nogil: + pass + + cdef void set_swap_interval(self, int interval) except *: + pass + + cdef list get_events(self): + return [] + + cdef int get_keystate(self) nogil: + return 0 + + cdef void toggle_fullscreen(self) nogil: + pass