"""SDL2 Rendering functionality.
.. versionadded:: 13.4
"""
from __future__ import annotations
import enum
from typing import Any, Final
import numpy as np
from numpy.typing import NDArray
from typing_extensions import Literal
import tcod.sdl.video
from tcod.cffi import ffi, lib
from tcod.sdl._internal import _check, _check_p, _required_version
[docs]
class TextureAccess(enum.IntEnum):
"""Determines how a texture is expected to be used."""
STATIC = 0
"""Texture rarely changes."""
STREAMING = 1
"""Texture frequently changes."""
TARGET = 2
"""Texture will be used as a render target."""
[docs]
class RendererFlip(enum.IntFlag):
"""Flip parameter for :any:`Renderer.copy`."""
NONE = 0
"""Default value, no flip."""
HORIZONTAL = 1
"""Flip the image horizontally."""
VERTICAL = 2
"""Flip the image vertically."""
[docs]
class BlendFactor(enum.IntEnum):
"""SDL blend factors.
.. seealso::
:any:`compose_blend_mode`
https://wiki.libsdl.org/SDL_BlendFactor
.. versionadded:: 13.5
"""
ZERO = 0x1
""""""
ONE = 0x2
""""""
SRC_COLOR = 0x3
""""""
ONE_MINUS_SRC_COLOR = 0x4
""""""
SRC_ALPHA = 0x5
""""""
ONE_MINUS_SRC_ALPHA = 0x6
""""""
DST_COLOR = 0x7
""""""
ONE_MINUS_DST_COLOR = 0x8
""""""
DST_ALPHA = 0x9
""""""
ONE_MINUS_DST_ALPHA = 0xA
""""""
[docs]
class BlendOperation(enum.IntEnum):
"""SDL blend operations.
.. seealso::
:any:`compose_blend_mode`
https://wiki.libsdl.org/SDL_BlendOperation
.. versionadded:: 13.5
"""
ADD = 0x1
"""dest + source"""
SUBTRACT = 0x2
"""dest - source"""
REV_SUBTRACT = 0x3
"""source - dest"""
MINIMUM = 0x4
"""min(dest, source)"""
MAXIMUM = 0x5
"""max(dest, source)"""
[docs]
class BlendMode(enum.IntEnum):
"""SDL blend modes.
.. seealso::
:any:`Texture.blend_mode`
:any:`Renderer.draw_blend_mode`
:any:`compose_blend_mode`
.. versionadded:: 13.5
"""
NONE = 0x00000000
""""""
BLEND = 0x00000001
""""""
ADD = 0x00000002
""""""
MOD = 0x00000004
""""""
INVALID = 0x7FFFFFFF
""""""
[docs]
def compose_blend_mode( # noqa: PLR0913
source_color_factor: BlendFactor,
dest_color_factor: BlendFactor,
color_operation: BlendOperation,
source_alpha_factor: BlendFactor,
dest_alpha_factor: BlendFactor,
alpha_operation: BlendOperation,
) -> BlendMode:
"""Return a custom blend mode composed of the given factors and operations.
.. seealso::
https://wiki.libsdl.org/SDL_ComposeCustomBlendMode
.. versionadded:: 13.5
"""
return BlendMode(
lib.SDL_ComposeCustomBlendMode(
source_color_factor,
dest_color_factor,
color_operation,
source_alpha_factor,
dest_alpha_factor,
alpha_operation,
)
)
[docs]
class Texture:
"""SDL hardware textures.
Create a new texture using :any:`Renderer.new_texture` or :any:`Renderer.upload_texture`.
"""
def __init__(self, sdl_texture_p: Any, sdl_renderer_p: Any = None) -> None:
"""Encapsulate an SDL_Texture pointer. This function is private."""
self.p = sdl_texture_p
self._sdl_renderer_p = sdl_renderer_p # Keep alive.
query = self._query()
self.format: Final[int] = query[0]
"""Texture format, read only."""
self.access: Final[TextureAccess] = TextureAccess(query[1])
"""Texture access mode, read only.
.. versionchanged:: 13.5
Attribute is now a :any:`TextureAccess` value.
"""
self.width: Final[int] = query[2]
"""Texture pixel width, read only."""
self.height: Final[int] = query[3]
"""Texture pixel height, read only."""
[docs]
def __eq__(self, other: object) -> bool:
"""Return True if compared to the same texture."""
if isinstance(other, Texture):
return bool(self.p == other.p)
return NotImplemented
def _query(self) -> tuple[int, int, int, int]:
"""Return (format, access, width, height)."""
format = ffi.new("uint32_t*")
buffer = ffi.new("int[3]")
lib.SDL_QueryTexture(self.p, format, buffer, buffer + 1, buffer + 2)
return int(format[0]), int(buffer[0]), int(buffer[1]), int(buffer[2])
[docs]
def update(self, pixels: NDArray[Any], rect: tuple[int, int, int, int] | None = None) -> None:
"""Update the pixel data of this texture.
.. versionadded:: 13.5
"""
if rect is None:
rect = (0, 0, self.width, self.height)
assert pixels.shape[:2] == (self.height, self.width)
if not pixels[0].flags.c_contiguous:
pixels = np.ascontiguousarray(pixels)
_check(lib.SDL_UpdateTexture(self.p, (rect,), ffi.cast("void*", pixels.ctypes.data), pixels.strides[0]))
@property
def alpha_mod(self) -> int:
"""Texture alpha modulate value, can be set to 0 - 255."""
out = ffi.new("uint8_t*")
_check(lib.SDL_GetTextureAlphaMod(self.p, out))
return int(out[0])
@alpha_mod.setter
def alpha_mod(self, value: int) -> None:
_check(lib.SDL_SetTextureAlphaMod(self.p, value))
@property
def blend_mode(self) -> BlendMode:
"""Texture blend mode, can be set.
.. versionchanged:: 13.5
Property now returns a BlendMode instance.
"""
out = ffi.new("SDL_BlendMode*")
_check(lib.SDL_GetTextureBlendMode(self.p, out))
return BlendMode(out[0])
@blend_mode.setter
def blend_mode(self, value: int) -> None:
_check(lib.SDL_SetTextureBlendMode(self.p, value))
@property
def color_mod(self) -> tuple[int, int, int]:
"""Texture RGB color modulate values, can be set."""
rgb = ffi.new("uint8_t[3]")
_check(lib.SDL_GetTextureColorMod(self.p, rgb, rgb + 1, rgb + 2))
return int(rgb[0]), int(rgb[1]), int(rgb[2])
@color_mod.setter
def color_mod(self, rgb: tuple[int, int, int]) -> None:
_check(lib.SDL_SetTextureColorMod(self.p, rgb[0], rgb[1], rgb[2]))
class _RestoreTargetContext:
"""A context manager which tracks the current render target and restores it on exiting."""
def __init__(self, renderer: Renderer) -> None:
self.renderer = renderer
self.old_texture_p = lib.SDL_GetRenderTarget(renderer.p)
def __enter__(self) -> None:
pass
def __exit__(self, *_: object) -> None:
_check(lib.SDL_SetRenderTarget(self.renderer.p, self.old_texture_p))
[docs]
class Renderer:
"""SDL Renderer."""
def __init__(self, sdl_renderer_p: Any) -> None:
"""Encapsulate an SDL_Renderer pointer. This function is private."""
if ffi.typeof(sdl_renderer_p) is not ffi.typeof("struct SDL_Renderer*"):
msg = f"Expected a {ffi.typeof('struct SDL_Window*')} type (was {ffi.typeof(sdl_renderer_p)})."
raise TypeError(msg)
if not sdl_renderer_p:
msg = "C pointer must not be null."
raise TypeError(msg)
self.p = sdl_renderer_p
[docs]
def __eq__(self, other: object) -> bool:
"""Return True if compared to the same renderer."""
if isinstance(other, Renderer):
return bool(self.p == other.p)
return NotImplemented
[docs]
def copy( # noqa: PLR0913
self,
texture: Texture,
source: tuple[float, float, float, float] | None = None,
dest: tuple[float, float, float, float] | None = None,
angle: float = 0,
center: tuple[float, float] | None = None,
flip: RendererFlip = RendererFlip.NONE,
) -> None:
"""Copy a texture to the rendering target.
Args:
texture: The texture to copy onto the current texture target.
source: The (x, y, width, height) region of `texture` to copy. If None then the entire texture is copied.
dest: The (x, y, width, height) region of the target. If None then the entire target is drawn over.
angle: The angle in degrees to rotate the image clockwise.
center: The (x, y) point where rotation is applied. If None then the center of `dest` is used.
flip: Flips the `texture` when drawing it.
.. versionchanged:: 13.5
`source` and `dest` can now be float tuples.
Added the `angle`, `center`, and `flip` parameters.
"""
_check(
lib.SDL_RenderCopyExF(
self.p,
texture.p,
(source,) if source is not None else ffi.NULL,
(dest,) if dest is not None else ffi.NULL,
angle,
(center,) if center is not None else ffi.NULL,
flip,
)
)
[docs]
def present(self) -> None:
"""Present the currently rendered image to the screen."""
lib.SDL_RenderPresent(self.p)
[docs]
def set_render_target(self, texture: Texture) -> _RestoreTargetContext:
"""Change the render target to `texture`, returns a context that will restore the original target when exited."""
restore = _RestoreTargetContext(self)
_check(lib.SDL_SetRenderTarget(self.p, texture.p))
return restore
[docs]
def new_texture(self, width: int, height: int, *, format: int | None = None, access: int | None = None) -> Texture:
"""Allocate and return a new Texture for this renderer.
Args:
width: The pixel width of the new texture.
height: The pixel height of the new texture.
format: The format the new texture.
access: The access mode of the texture. Defaults to :any:`TextureAccess.STATIC`.
See :any:`TextureAccess` for more options.
"""
if format is None:
format = 0
if access is None:
access = int(lib.SDL_TEXTUREACCESS_STATIC)
texture_p = ffi.gc(lib.SDL_CreateTexture(self.p, format, access, width, height), lib.SDL_DestroyTexture)
return Texture(texture_p, self.p)
[docs]
def upload_texture(self, pixels: NDArray[Any], *, format: int | None = None, access: int | None = None) -> Texture:
"""Return a new Texture from an array of pixels.
Args:
pixels: An RGB or RGBA array of pixels in row-major order.
format: The format of `pixels` when it isn't a simple RGB or RGBA array.
access: The access mode of the texture. Defaults to :any:`TextureAccess.STATIC`.
See :any:`TextureAccess` for more options.
"""
if format is None:
assert len(pixels.shape) == 3 # noqa: PLR2004
assert pixels.dtype == np.uint8
if pixels.shape[2] == 4: # noqa: PLR2004
format = int(lib.SDL_PIXELFORMAT_RGBA32)
elif pixels.shape[2] == 3: # noqa: PLR2004
format = int(lib.SDL_PIXELFORMAT_RGB24)
else:
msg = f"Can't determine the format required for an array of shape {pixels.shape}."
raise TypeError(msg)
texture = self.new_texture(pixels.shape[1], pixels.shape[0], format=format, access=access)
if not pixels[0].flags["C_CONTIGUOUS"]:
pixels = np.ascontiguousarray(pixels)
_check(
lib.SDL_UpdateTexture(texture.p, ffi.NULL, ffi.cast("const void*", pixels.ctypes.data), pixels.strides[0])
)
return texture
@property
def draw_color(self) -> tuple[int, int, int, int]:
"""Get or set the active RGBA draw color for this renderer.
.. versionadded:: 13.5
"""
rgba = ffi.new("uint8_t[4]")
_check(lib.SDL_GetRenderDrawColor(self.p, rgba, rgba + 1, rgba + 2, rgba + 3))
return tuple(rgba)
@draw_color.setter
def draw_color(self, rgba: tuple[int, int, int, int]) -> None:
_check(lib.SDL_SetRenderDrawColor(self.p, *rgba))
@property
def draw_blend_mode(self) -> BlendMode:
"""Get or set the active blend mode of this renderer.
.. versionadded:: 13.5
"""
out = ffi.new("SDL_BlendMode*")
_check(lib.SDL_GetRenderDrawBlendMode(self.p, out))
return BlendMode(out[0])
@draw_blend_mode.setter
def draw_blend_mode(self, value: int) -> None:
_check(lib.SDL_SetRenderDrawBlendMode(self.p, value))
@property
def output_size(self) -> tuple[int, int]:
"""Get the (width, height) pixel resolution of the rendering context.
.. seealso::
https://wiki.libsdl.org/SDL_GetRendererOutputSize
.. versionadded:: 13.5
"""
out = ffi.new("int[2]")
_check(lib.SDL_GetRendererOutputSize(self.p, out, out + 1))
return out[0], out[1]
@property
def clip_rect(self) -> tuple[int, int, int, int] | None:
"""Get or set the clipping rectangle of this renderer.
Set to None to disable clipping.
.. versionadded:: 13.5
"""
if not lib.SDL_RenderIsClipEnabled(self.p):
return None
rect = ffi.new("SDL_Rect*")
lib.SDL_RenderGetClipRect(self.p, rect)
return rect.x, rect.y, rect.w, rect.h
@clip_rect.setter
def clip_rect(self, rect: tuple[int, int, int, int] | None) -> None:
rect_p = ffi.NULL if rect is None else ffi.new("SDL_Rect*", rect)
_check(lib.SDL_RenderSetClipRect(self.p, rect_p))
@property
def integer_scaling(self) -> bool:
"""Get or set if this renderer enforces integer scaling.
.. seealso::
https://wiki.libsdl.org/SDL_RenderSetIntegerScale
.. versionadded:: 13.5
"""
return bool(lib.SDL_RenderGetIntegerScale(self.p))
@integer_scaling.setter
def integer_scaling(self, enable: bool) -> None:
_check(lib.SDL_RenderSetIntegerScale(self.p, enable))
@property
def logical_size(self) -> tuple[int, int]:
"""Get or set a device independent (width, height) resolution.
Might be (0, 0) if a resolution was never assigned.
.. seealso::
https://wiki.libsdl.org/SDL_RenderSetLogicalSize
.. versionadded:: 13.5
"""
out = ffi.new("int[2]")
lib.SDL_RenderGetLogicalSize(self.p, out, out + 1)
return out[0], out[1]
@logical_size.setter
def logical_size(self, size: tuple[int, int]) -> None:
_check(lib.SDL_RenderSetLogicalSize(self.p, *size))
@property
def scale(self) -> tuple[float, float]:
"""Get or set an (x_scale, y_scale) multiplier for drawing.
.. seealso::
https://wiki.libsdl.org/SDL_RenderSetScale
.. versionadded:: 13.5
"""
out = ffi.new("float[2]")
lib.SDL_RenderGetScale(self.p, out, out + 1)
return out[0], out[1]
@scale.setter
def scale(self, scale: tuple[float, float]) -> None:
_check(lib.SDL_RenderSetScale(self.p, *scale))
@property
def viewport(self) -> tuple[int, int, int, int] | None:
"""Get or set the drawing area for the current rendering target.
.. seealso::
https://wiki.libsdl.org/SDL_RenderSetViewport
.. versionadded:: 13.5
"""
rect = ffi.new("SDL_Rect*")
lib.SDL_RenderGetViewport(self.p, rect)
return rect.x, rect.y, rect.w, rect.h
@viewport.setter
def viewport(self, rect: tuple[int, int, int, int] | None) -> None:
_check(lib.SDL_RenderSetViewport(self.p, (rect,)))
[docs]
@_required_version((2, 0, 18))
def set_vsync(self, enable: bool) -> None:
"""Enable or disable VSync for this renderer.
.. versionadded:: 13.5
"""
_check(lib.SDL_RenderSetVSync(self.p, enable))
[docs]
def read_pixels(
self,
*,
rect: tuple[int, int, int, int] | None = None,
format: int | Literal["RGB", "RGBA"] = "RGBA",
out: NDArray[np.uint8] | None = None,
) -> NDArray[np.uint8]:
"""Fetch the pixel contents of the current rendering target to an array.
By default returns an RGBA pixel array of the full target in the shape: ``(height, width, rgba)``.
The target can be changed with :any:`set_render_target`
Args:
rect: The ``(left, top, width, height)`` region of the target to fetch, or None for the entire target.
format: The pixel format. Defaults to ``"RGBA"``.
out: The output array.
Can be None or must be an ``np.uint8`` array of shape: ``(height, width, channels)``.
Must be C contiguous along the ``(width, channels)`` axes.
This operation is slow due to coping from VRAM to RAM.
When reading the main rendering target this should be called after rendering and before :any:`present`.
See https://wiki.libsdl.org/SDL2/SDL_RenderReadPixels
Returns:
The output uint8 array of shape: ``(height, width, channels)`` with the fetched pixels.
.. versionadded:: 15.0
"""
FORMATS: Final = {"RGB": lib.SDL_PIXELFORMAT_RGB24, "RGBA": lib.SDL_PIXELFORMAT_RGBA32}
sdl_format = FORMATS.get(format) if isinstance(format, str) else format
if rect is None:
texture_p = lib.SDL_GetRenderTarget(self.p)
if texture_p:
texture = Texture(texture_p)
rect = (0, 0, texture.width, texture.height)
else:
rect = (0, 0, *self.output_size)
width, height = rect[2:4]
if out is None:
if sdl_format == lib.SDL_PIXELFORMAT_RGBA32:
out = np.empty((height, width, 4), dtype=np.uint8)
elif sdl_format == lib.SDL_PIXELFORMAT_RGB24:
out = np.empty((height, width, 3), dtype=np.uint8)
else:
msg = f"Pixel format {format!r} not supported by tcod."
raise TypeError(msg)
if out.dtype != np.uint8:
msg = "`out` must be a uint8 array."
raise TypeError(msg)
expected_shape = (height, width, {lib.SDL_PIXELFORMAT_RGB24: 3, lib.SDL_PIXELFORMAT_RGBA32: 4}[sdl_format])
if out.shape != expected_shape:
msg = f"Expected `out` to be an array of shape {expected_shape}, got {out.shape} instead."
raise TypeError(msg)
if not out[0].flags.c_contiguous:
msg = "`out` array must be C contiguous."
_check(
lib.SDL_RenderReadPixels(
self.p,
(rect,),
sdl_format,
ffi.cast("void*", out.ctypes.data),
out.strides[0],
)
)
return out
[docs]
def clear(self) -> None:
"""Clear the current render target with :any:`draw_color`.
.. versionadded:: 13.5
"""
_check(lib.SDL_RenderClear(self.p))
[docs]
def fill_rect(self, rect: tuple[float, float, float, float]) -> None:
"""Fill a rectangle with :any:`draw_color`.
.. versionadded:: 13.5
"""
_check(lib.SDL_RenderFillRectF(self.p, (rect,)))
[docs]
def draw_rect(self, rect: tuple[float, float, float, float]) -> None:
"""Draw a rectangle outline.
.. versionadded:: 13.5
"""
_check(lib.SDL_RenderDrawRectF(self.p, (rect,)))
[docs]
def draw_point(self, xy: tuple[float, float]) -> None:
"""Draw a point.
.. versionadded:: 13.5
"""
_check(lib.SDL_RenderDrawPointF(self.p, (xy,)))
[docs]
def draw_line(self, start: tuple[float, float], end: tuple[float, float]) -> None:
"""Draw a single line.
.. versionadded:: 13.5
"""
_check(lib.SDL_RenderDrawLineF(self.p, *start, *end))
[docs]
def fill_rects(self, rects: NDArray[np.intc | np.float32]) -> None:
"""Fill multiple rectangles from an array.
.. versionadded:: 13.5
"""
assert len(rects.shape) == 2 # noqa: PLR2004
assert rects.shape[1] == 4 # noqa: PLR2004
rects = np.ascontiguousarray(rects)
if rects.dtype == np.intc:
_check(lib.SDL_RenderFillRects(self.p, tcod.ffi.from_buffer("SDL_Rect*", rects), rects.shape[0]))
elif rects.dtype == np.float32:
_check(lib.SDL_RenderFillRectsF(self.p, tcod.ffi.from_buffer("SDL_FRect*", rects), rects.shape[0]))
else:
msg = f"Array must be an np.intc or np.float32 type, got {rects.dtype}."
raise TypeError(msg)
[docs]
def draw_rects(self, rects: NDArray[np.intc | np.float32]) -> None:
"""Draw multiple outlined rectangles from an array.
.. versionadded:: 13.5
"""
assert len(rects.shape) == 2 # noqa: PLR2004
assert rects.shape[1] == 4 # noqa: PLR2004
rects = np.ascontiguousarray(rects)
if rects.dtype == np.intc:
_check(lib.SDL_RenderDrawRects(self.p, tcod.ffi.from_buffer("SDL_Rect*", rects), rects.shape[0]))
elif rects.dtype == np.float32:
_check(lib.SDL_RenderDrawRectsF(self.p, tcod.ffi.from_buffer("SDL_FRect*", rects), rects.shape[0]))
else:
msg = f"Array must be an np.intc or np.float32 type, got {rects.dtype}."
raise TypeError(msg)
[docs]
def draw_points(self, points: NDArray[np.intc | np.float32]) -> None:
"""Draw an array of points.
.. versionadded:: 13.5
"""
assert len(points.shape) == 2 # noqa: PLR2004
assert points.shape[1] == 2 # noqa: PLR2004
points = np.ascontiguousarray(points)
if points.dtype == np.intc:
_check(lib.SDL_RenderDrawRects(self.p, tcod.ffi.from_buffer("SDL_Point*", points), points.shape[0]))
elif points.dtype == np.float32:
_check(lib.SDL_RenderDrawRectsF(self.p, tcod.ffi.from_buffer("SDL_FPoint*", points), points.shape[0]))
else:
msg = f"Array must be an np.intc or np.float32 type, got {points.dtype}."
raise TypeError(msg)
[docs]
def draw_lines(self, points: NDArray[np.intc | np.float32]) -> None:
"""Draw a connected series of lines from an array.
.. versionadded:: 13.5
"""
assert len(points.shape) == 2 # noqa: PLR2004
assert points.shape[1] == 2 # noqa: PLR2004
points = np.ascontiguousarray(points)
if points.dtype == np.intc:
_check(lib.SDL_RenderDrawRects(self.p, tcod.ffi.from_buffer("SDL_Point*", points), points.shape[0] - 1))
elif points.dtype == np.float32:
_check(lib.SDL_RenderDrawRectsF(self.p, tcod.ffi.from_buffer("SDL_FPoint*", points), points.shape[0] - 1))
else:
msg = f"Array must be an np.intc or np.float32 type, got {points.dtype}."
raise TypeError(msg)
[docs]
@_required_version((2, 0, 18))
def geometry( # noqa: PLR0913
self,
texture: Texture | None,
xy: NDArray[np.float32],
color: NDArray[np.uint8],
uv: NDArray[np.float32],
indices: NDArray[np.uint8 | np.uint16 | np.uint32] | None = None,
) -> None:
"""Render triangles from texture and vertex data.
.. versionadded:: 13.5
"""
assert xy.dtype == np.float32
assert len(xy.shape) == 2 # noqa: PLR2004
assert xy.shape[1] == 2 # noqa: PLR2004
assert xy[0].flags.c_contiguous
assert color.dtype == np.uint8
assert len(color.shape) == 2 # noqa: PLR2004
assert color.shape[1] == 4 # noqa: PLR2004
assert color[0].flags.c_contiguous
assert uv.dtype == np.float32
assert len(uv.shape) == 2 # noqa: PLR2004
assert uv.shape[1] == 2 # noqa: PLR2004
assert uv[0].flags.c_contiguous
if indices is not None:
assert indices.dtype.type in (np.uint8, np.uint16, np.uint32, np.int8, np.int16, np.int32)
indices = np.ascontiguousarray(indices)
assert len(indices.shape) == 1
assert xy.shape[0] == color.shape[0] == uv.shape[0]
_check(
lib.SDL_RenderGeometryRaw(
self.p,
texture.p if texture else ffi.NULL,
ffi.cast("float*", xy.ctypes.data),
xy.strides[0],
ffi.cast("uint8_t*", color.ctypes.data),
color.strides[0],
ffi.cast("float*", uv.ctypes.data),
uv.strides[0],
xy.shape[0], # Number of vertices.
ffi.cast("void*", indices.ctypes.data) if indices is not None else ffi.NULL,
indices.size if indices is not None else 0,
indices.itemsize if indices is not None else 0,
)
)
[docs]
def new_renderer(
window: tcod.sdl.video.Window,
*,
driver: int | None = None,
software: bool = False,
vsync: bool = True,
target_textures: bool = False,
) -> Renderer:
"""Initialize and return a new SDL Renderer.
Args:
window: The window that this renderer will be attached to.
driver: Force SDL to use a specific video driver.
software: If True then a software renderer will be forced. By default a hardware renderer is used.
vsync: If True then Vsync will be enabled.
target_textures: If True then target textures can be used by the renderer.
Example::
# Start by creating a window.
sdl_window = tcod.sdl.video.new_window(640, 480)
# Create a renderer with target texture support.
sdl_renderer = tcod.sdl.render.new_renderer(sdl_window, target_textures=True)
.. seealso::
:func:`tcod.sdl.video.new_window`
"""
driver = driver if driver is not None else -1
flags = 0
if vsync:
flags |= int(lib.SDL_RENDERER_PRESENTVSYNC)
if target_textures:
flags |= int(lib.SDL_RENDERER_TARGETTEXTURE)
flags |= int(lib.SDL_RENDERER_SOFTWARE) if software else int(lib.SDL_RENDERER_ACCELERATED)
renderer_p = _check_p(ffi.gc(lib.SDL_CreateRenderer(window.p, driver, flags), lib.SDL_DestroyRenderer))
return Renderer(renderer_p)