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

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

View file

@ -8,10 +8,9 @@ from c3nav.mapdata.cache import MapHistory
from c3nav.mapdata.models import MapUpdate
from c3nav.mapdata.render.data import get_level_render_data
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):
self.level = level
self.minx = minx
@ -64,8 +63,8 @@ class ImageRenderer:
def cache_key(self):
return self.update_cache_key + ':' + self.access_cache_key
def render(self):
svg = SVGEngine(self.width, self.height, self.minx, self.miny,
def render(self, engine_cls):
engine = engine_cls(self.width, self.height, self.minx, self.miny,
scale=self.scale, buffer=1, background='#DCDCDC')
# add no access restriction to “unlocked“ access restrictions so lookup gets easier
@ -89,7 +88,7 @@ class ImageRenderer:
# 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
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'),
stroke=StrokeAttribs('rgba(0, 0, 0, 0.15)', 0.05, min_px=0.2))
@ -98,7 +97,7 @@ class ImageRenderer:
areas = tuple(area for access_restriction, area in areas.items()
if access_restriction in unlocked_access_restrictions)
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,
walls = None
@ -106,13 +105,13 @@ class ImageRenderer:
walls = bbox.intersection(geoms.walls.union(add_walls))
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:
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))
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.middleware import no_language
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()
@ -50,7 +51,7 @@ def tile(request, level, zoom, x, y, format):
access_permissions = get_tile_access_cookie(request)
# 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
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'
if data is None:
svg = renderer.render()
svg = renderer.render(ImageRenderEngine)
if format == 'svg':
data = svg.get_xml()
filemode = 'w'

View file

@ -73,8 +73,11 @@ else:
debug_fallback = "runserver" in sys.argv
DEBUG = config.getboolean('django', 'debug', fallback=debug_fallback)
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')
CACHE_TILES = config.get('c3nav', 'cache_tiles', fallback=not DEBUG)
CACHE_RESOLUTION = config.get('c3nav', 'cache_resolution', fallback=4)