add base for OpenGL map render engine

This commit is contained in:
Laura Klünder 2017-11-06 11:18:45 +01:00
parent 80fb9e2df7
commit a05c7a5a3c
7 changed files with 108 additions and 20 deletions

View file

@ -1,2 +1,2 @@
from c3nav.mapdata.render.renderer import ImageRenderer # noqa from c3nav.mapdata.render.renderer import MapRenderer # noqa
from c3nav.mapdata.render.utils import get_render_level_ids, set_tile_access_cookie, get_tile_access_cookie # noqa from c3nav.mapdata.render.utils import get_render_level_ids, set_tile_access_cookie, get_tile_access_cookie # noqa

View file

@ -0,0 +1,24 @@
from django.conf import settings
from django.core import checks
from c3nav.mapdata.render.engines.svg import SVGEngine # noqa
@checks.register()
def check_image_renderer(app_configs, **kwargs):
errors = []
if settings.IMAGE_RENDERER not in ('svg', 'opengl'):
errors.append(
checks.Error(
'Invalid image renderer: '+settings.IMAGE_RENDERER,
obj='settings.IMAGE_RENDERER',
id='c3nav.mapdata.E001',
)
)
return errors
if settings.IMAGE_RENDERER == 'opengl':
from c3nav.mapdata.render.engines.opengl import OpenGLEngine as ImageRenderEngine # noqa
else:
from c3nav.mapdata.render.engines.svg import SVGEngine as ImageRenderEngine # noqa

View file

@ -0,0 +1,61 @@
import io
import ModernGL
import numpy as np
from PIL import Image
from c3nav.mapdata.render.engines.base import RenderEngine
class OpenGLEngine(RenderEngine):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.vertices = []
self.ctx = ModernGL.create_standalone_context()
self.color_rbo = self.ctx.renderbuffer((self.width, self.height))
self.depth_rbo = self.ctx.depth_renderbuffer((self.width, self.height))
self.fbo = self.ctx.framebuffer([self.color_rbo], self.depth_rbo)
self.fbo.use()
self.ctx.clear(*(i/255 for i in self.background_rgb))
self.prog = self.ctx.program([
self.ctx.vertex_shader('''
#version 330
in vec2 in_vert;
in vec3 in_color;
out vec3 v_color;
void main() {
gl_Position = vec4(in_vert, 0.0, 1.0);
v_color = in_color;
}
'''),
self.ctx.fragment_shader('''
#version 330
in vec3 v_color;
out vec4 f_color;
void main() {
f_color = vec4(v_color, 1.0);
}
'''),
])
def _add_geometry(self, geometry, fill=None, stroke=None, altitude=None, height=None, shape_cache_key=None):
pass
def get_png(self) -> bytes:
if self.vertices:
vbo = self.ctx.buffer(np.hstack(self.vertices).tobytes())
# We control the 'in_vert' and `in_color' variables
vao = self.ctx.simple_vertex_array(self.prog, vbo, ['in_vert', 'in_color'])
vao.render()
img = Image.frombytes('RGB', (self.width, self.height), self.fbo.read(components=3))
f = io.BytesIO()
img.save(f, 'PNG')
f.seek(0)
return f.read()

View file

@ -6,9 +6,9 @@ from itertools import chain
from typing import Optional from typing import Optional
import numpy as np import numpy as np
from PIL import Image
from django.conf import settings from django.conf import settings
from django.core import checks from django.core import checks
from PIL import Image
from shapely.affinity import translate from shapely.affinity import translate
from shapely.geometry import LineString, Polygon from shapely.geometry import LineString, Polygon
@ -30,7 +30,7 @@ def check_svg_renderer(app_configs, **kwargs):
checks.Error( checks.Error(
'Invalid SVG renderer: '+settings.SVG_RENDERER, 'Invalid SVG renderer: '+settings.SVG_RENDERER,
obj='settings.SVG_RENDERER', obj='settings.SVG_RENDERER',
id='c3nav.mapdata.E001', id='c3nav.mapdata.E002',
) )
) )
return errors return errors

View file

@ -8,10 +8,9 @@ from c3nav.mapdata.cache import MapHistory
from c3nav.mapdata.models import MapUpdate from c3nav.mapdata.models import MapUpdate
from c3nav.mapdata.render.data import get_level_render_data from c3nav.mapdata.render.data import get_level_render_data
from c3nav.mapdata.render.engines.base import FillAttribs, StrokeAttribs from c3nav.mapdata.render.engines.base import FillAttribs, StrokeAttribs
from c3nav.mapdata.render.engines.svg import SVGEngine
class ImageRenderer: class MapRenderer:
def __init__(self, level, minx, miny, maxx, maxy, scale=1, access_permissions=None): def __init__(self, level, minx, miny, maxx, maxy, scale=1, access_permissions=None):
self.level = level self.level = level
self.minx = minx self.minx = minx
@ -64,9 +63,9 @@ class ImageRenderer:
def cache_key(self): def cache_key(self):
return self.update_cache_key + ':' + self.access_cache_key return self.update_cache_key + ':' + self.access_cache_key
def render(self): def render(self, engine_cls):
svg = SVGEngine(self.width, self.height, self.minx, self.miny, engine = engine_cls(self.width, self.height, self.minx, self.miny,
scale=self.scale, buffer=1, background='#DCDCDC') scale=self.scale, buffer=1, background='#DCDCDC')
# add no access restriction to “unlocked“ access restrictions so lookup gets easier # add no access restriction to “unlocked“ access restrictions so lookup gets easier
unlocked_access_restrictions = self.unlocked_access_restrictions | set([None]) unlocked_access_restrictions = self.unlocked_access_restrictions | set([None])
@ -89,16 +88,16 @@ class ImageRenderer:
# render altitude areas in default ground color and add ground colors to each one afterwards # render altitude areas in default ground color and add ground colors to each one afterwards
# shadows are directly calculated and added by the SVGImage class # shadows are directly calculated and added by the SVGImage class
for altitudearea in geoms.altitudeareas: for altitudearea in geoms.altitudeareas:
svg.add_geometry(bbox.intersection(altitudearea.geometry.difference(crop_areas)), engine.add_geometry(bbox.intersection(altitudearea.geometry.difference(crop_areas)),
altitude=altitudearea.altitude, fill=FillAttribs('#eeeeee'), altitude=altitudearea.altitude, fill=FillAttribs('#eeeeee'),
stroke=StrokeAttribs('rgba(0, 0, 0, 0.15)', 0.05, min_px=0.2)) stroke=StrokeAttribs('rgba(0, 0, 0, 0.15)', 0.05, min_px=0.2))
for color, areas in altitudearea.colors.items(): for color, areas in altitudearea.colors.items():
# only select ground colors if their access restriction is unlocked # only select ground colors if their access restriction is unlocked
areas = tuple(area for access_restriction, area in areas.items() areas = tuple(area for access_restriction, area in areas.items()
if access_restriction in unlocked_access_restrictions) if access_restriction in unlocked_access_restrictions)
if areas: if areas:
svg.add_geometry(bbox.intersection(unary_union(areas)), fill=FillAttribs(color)) engine.add_geometry(bbox.intersection(unary_union(areas)), fill=FillAttribs(color))
# add walls, stroke_px makes sure that all walls are at least 1px thick on all zoom levels, # add walls, stroke_px makes sure that all walls are at least 1px thick on all zoom levels,
walls = None walls = None
@ -106,13 +105,13 @@ class ImageRenderer:
walls = bbox.intersection(geoms.walls.union(add_walls)) walls = bbox.intersection(geoms.walls.union(add_walls))
if walls is not None: if walls is not None:
svg.add_geometry(walls, height=default_height, fill=FillAttribs('#aaaaaa')) engine.add_geometry(walls, height=default_height, fill=FillAttribs('#aaaaaa'))
if not geoms.doors.is_empty: if not geoms.doors.is_empty:
svg.add_geometry(bbox.intersection(geoms.doors.difference(add_walls)), fill=FillAttribs('#ffffff'), engine.add_geometry(bbox.intersection(geoms.doors.difference(add_walls)), fill=FillAttribs('#ffffff'),
stroke=StrokeAttribs('#ffffff', 0.05, min_px=0.2)) stroke=StrokeAttribs('#ffffff', 0.05, min_px=0.2))
if walls is not None: if walls is not None:
svg.add_geometry(walls, stroke=StrokeAttribs('#666666', 0.05, min_px=0.2)) engine.add_geometry(walls, stroke=StrokeAttribs('#666666', 0.05, min_px=0.2))
return svg return engine

View file

@ -14,7 +14,8 @@ from shapely.geometry import box
from c3nav.mapdata.cache import MapHistory from c3nav.mapdata.cache import MapHistory
from c3nav.mapdata.middleware import no_language from c3nav.mapdata.middleware import no_language
from c3nav.mapdata.models import Level, MapUpdate, Source from c3nav.mapdata.models import Level, MapUpdate, Source
from c3nav.mapdata.render import ImageRenderer, get_render_level_ids, get_tile_access_cookie, set_tile_access_cookie from c3nav.mapdata.render import MapRenderer, get_render_level_ids, get_tile_access_cookie, set_tile_access_cookie
from c3nav.mapdata.render.engines import ImageRenderEngine
@no_language() @no_language()
@ -50,7 +51,7 @@ def tile(request, level, zoom, x, y, format):
access_permissions = get_tile_access_cookie(request) access_permissions = get_tile_access_cookie(request)
# init renderer # init renderer
renderer = ImageRenderer(level, minx, miny, maxx, maxy, scale=2**zoom, access_permissions=access_permissions) renderer = MapRenderer(level, minx, miny, maxx, maxy, scale=2 ** zoom, access_permissions=access_permissions)
tile_cache_key = renderer.cache_key tile_cache_key = renderer.cache_key
update_cache_key = renderer.update_cache_key update_cache_key = renderer.update_cache_key
@ -93,7 +94,7 @@ def tile(request, level, zoom, x, y, format):
content_type = 'image/svg+xml' if format == 'svg' else 'image/png' content_type = 'image/svg+xml' if format == 'svg' else 'image/png'
if data is None: if data is None:
svg = renderer.render() svg = renderer.render(ImageRenderEngine)
if format == 'svg': if format == 'svg':
data = svg.get_xml() data = svg.get_xml()
filemode = 'w' filemode = 'w'

View file

@ -73,8 +73,11 @@ else:
debug_fallback = "runserver" in sys.argv debug_fallback = "runserver" in sys.argv
DEBUG = config.getboolean('django', 'debug', fallback=debug_fallback) DEBUG = config.getboolean('django', 'debug', fallback=debug_fallback)
RENDER_SCALE = float(config.get('c3nav', 'render_scale', fallback=20.0)) RENDER_SCALE = float(config.get('c3nav', 'render_scale', fallback=20.0))
IMAGE_RENDERER = config.get('c3nav', 'image_renderer', fallback='svg')
SVG_RENDERER = config.get('c3nav', 'svg_renderer', fallback='rsvg-convert') SVG_RENDERER = config.get('c3nav', 'svg_renderer', fallback='rsvg-convert')
CACHE_TILES = config.get('c3nav', 'cache_tiles', fallback=not DEBUG) CACHE_TILES = config.get('c3nav', 'cache_tiles', fallback=not DEBUG)
CACHE_RESOLUTION = config.get('c3nav', 'cache_resolution', fallback=4) CACHE_RESOLUTION = config.get('c3nav', 'cache_resolution', fallback=4)