mirror of
https://github.com/maplibre/maplibre-rs.git
synced 2025-12-08 19:05:57 +00:00
Implement headless rendering
This commit is contained in:
parent
e59dacdb45
commit
1e63718dfc
@ -17,6 +17,9 @@ env_logger = "0.9"
|
||||
maplibre = { path = "../maplibre", version = "0.0.2" }
|
||||
maplibre-winit = { path = "../maplibre-winit", version = "0.0.1" }
|
||||
|
||||
tokio = "1.18"
|
||||
wgpu = "0.12"
|
||||
|
||||
tracing = { version = "0.1" }
|
||||
tracing-subscriber = { version = "0.3", optional = true }
|
||||
tracing-tracy = { version = "0.8", optional = true }
|
||||
|
||||
@ -1,8 +1,15 @@
|
||||
use maplibre::error::Error;
|
||||
use maplibre::io::scheduler::ScheduleMethod;
|
||||
use maplibre::io::source_client::HTTPClient;
|
||||
use maplibre::map_state::MapSchedule;
|
||||
use maplibre::platform::http_client::ReqwestHttpClient;
|
||||
use maplibre::platform::run_multithreaded;
|
||||
use maplibre::platform::schedule_method::TokioScheduleMethod;
|
||||
use maplibre::render::settings::RendererSettings;
|
||||
use maplibre::window::{MapWindow, MapWindowConfig, Runnable, WindowSize};
|
||||
use maplibre::MapBuilder;
|
||||
use maplibre_winit::winit::{WinitEventLoop, WinitMapWindow, WinitMapWindowConfig, WinitWindow};
|
||||
use wgpu::TextureFormat;
|
||||
|
||||
#[cfg(feature = "trace")]
|
||||
fn enable_tracing() {
|
||||
@ -13,6 +20,54 @@ fn enable_tracing() {
|
||||
|
||||
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
|
||||
}
|
||||
pub struct HeadlessMapWindowConfig;
|
||||
|
||||
impl MapWindowConfig for HeadlessMapWindowConfig {
|
||||
type MapWindow = HeadlessMapWindow;
|
||||
|
||||
fn create(&self) -> Self::MapWindow {
|
||||
Self::MapWindow {}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HeadlessMapWindow;
|
||||
|
||||
impl MapWindow for HeadlessMapWindow {
|
||||
type EventLoop = ();
|
||||
type RawWindow = ();
|
||||
|
||||
fn size(&self) -> WindowSize {
|
||||
WindowSize::new(1920, 1080).unwrap()
|
||||
}
|
||||
|
||||
fn inner(&self) -> &Self::RawWindow {
|
||||
&()
|
||||
}
|
||||
}
|
||||
|
||||
impl<MWC, SM, HC> Runnable<MWC, SM, HC> for HeadlessMapWindow
|
||||
where
|
||||
MWC: MapWindowConfig<MapWindow = HeadlessMapWindow>,
|
||||
SM: ScheduleMethod,
|
||||
HC: HTTPClient,
|
||||
{
|
||||
fn run(mut self, mut map_state: MapSchedule<MWC, SM, HC>, max_frames: Option<u64>) {
|
||||
let arc = map_state.map_context.renderer.device.clone();
|
||||
tokio::task::spawn_blocking(move || loop {
|
||||
arc.poll(wgpu::Maintain::Wait);
|
||||
});
|
||||
for i in 0..3 {
|
||||
match map_state.update_and_redraw() {
|
||||
Ok(_) => {}
|
||||
Err(Error::Render(e)) => {
|
||||
eprintln!("{}", e);
|
||||
if e.should_exit() {}
|
||||
}
|
||||
e => eprintln!("{:?}", e),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_in_window() {
|
||||
run_multithreaded(async {
|
||||
@ -27,11 +82,28 @@ fn run_in_window() {
|
||||
})
|
||||
}
|
||||
|
||||
fn run_headless() {
|
||||
run_multithreaded(async {
|
||||
MapBuilder::new()
|
||||
.with_map_window_config(HeadlessMapWindowConfig)
|
||||
.with_http_client(ReqwestHttpClient::new(None))
|
||||
.with_schedule_method(TokioScheduleMethod::new())
|
||||
.with_renderer_settings(RendererSettings {
|
||||
texture_format: TextureFormat::Rgba8UnormSrgb,
|
||||
..RendererSettings::default()
|
||||
})
|
||||
.build()
|
||||
.initialize_headless()
|
||||
.await
|
||||
.run()
|
||||
})
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));
|
||||
|
||||
#[cfg(feature = "trace")]
|
||||
enable_tracing();
|
||||
|
||||
run_in_window()
|
||||
run_headless()
|
||||
}
|
||||
|
||||
@ -47,10 +47,6 @@ impl WinitMapWindowConfig {
|
||||
}
|
||||
}
|
||||
|
||||
impl MapWindowConfig for WinitMapWindowConfig {
|
||||
type MapWindow = WinitMapWindow;
|
||||
}
|
||||
|
||||
pub struct WinitMapWindow {
|
||||
window: WinitWindow,
|
||||
event_loop: Option<WinitEventLoop>,
|
||||
|
||||
@ -10,25 +10,11 @@ use super::WinitMapWindow;
|
||||
use super::WinitWindow;
|
||||
|
||||
use super::WinitMapWindowConfig;
|
||||
use maplibre::window::{MapWindow, WindowSize};
|
||||
use maplibre::window::{MapWindow, MapWindowConfig, WindowSize};
|
||||
|
||||
impl MapWindow for WinitMapWindow {
|
||||
type EventLoop = WinitEventLoop;
|
||||
type Window = WinitWindow;
|
||||
type MapWindowConfig = WinitMapWindowConfig;
|
||||
|
||||
fn create(map_window_config: &Self::MapWindowConfig) -> Self {
|
||||
let event_loop = WinitEventLoop::new();
|
||||
let window = WindowBuilder::new()
|
||||
.with_title(&map_window_config.title)
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
Self {
|
||||
window,
|
||||
event_loop: Some(event_loop),
|
||||
}
|
||||
}
|
||||
type RawWindow = WinitWindow;
|
||||
|
||||
fn size(&self) -> WindowSize {
|
||||
let size = self.window.inner_size();
|
||||
@ -44,7 +30,24 @@ impl MapWindow for WinitMapWindow {
|
||||
window_size
|
||||
}
|
||||
|
||||
fn inner(&self) -> &Self::Window {
|
||||
fn inner(&self) -> &Self::RawWindow {
|
||||
&self.window
|
||||
}
|
||||
}
|
||||
|
||||
impl MapWindowConfig for WinitMapWindowConfig {
|
||||
type MapWindow = WinitMapWindow;
|
||||
|
||||
fn create(&self) -> Self::MapWindow {
|
||||
let event_loop = WinitEventLoop::new();
|
||||
let window = WindowBuilder::new()
|
||||
.with_title(&self.title)
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
Self::MapWindow {
|
||||
window,
|
||||
event_loop: Some(event_loop),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,10 +10,40 @@ use winit::platform::web::WindowBuilderExtWebSys;
|
||||
|
||||
impl MapWindow for WinitMapWindow {
|
||||
type EventLoop = WinitEventLoop;
|
||||
type Window = WinitWindow;
|
||||
type RawWindow = WinitWindow;
|
||||
type MapWindowConfig = WinitMapWindowConfig;
|
||||
|
||||
fn create(map_window_config: &Self::MapWindowConfig) -> Self {
|
||||
fn size(&self) -> WindowSize {
|
||||
let size = self.window.inner_size();
|
||||
|
||||
WindowSize::new(size.width, size.height).expect("failed to get window dimensions.")
|
||||
}
|
||||
|
||||
fn inner(&self) -> &Self::RawWindow {
|
||||
&self.window
|
||||
}
|
||||
}
|
||||
|
||||
fn create(map_window_config: &Self::MapWindowConfig) -> Self {
|
||||
let event_loop = WinitEventLoop::new();
|
||||
|
||||
let window: winit::window::Window = WindowBuilder::new()
|
||||
.with_canvas(Some(get_canvas(&map_window_config.canvas_id)))
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let size = get_body_size().unwrap();
|
||||
window.set_inner_size(size);
|
||||
Self {
|
||||
window,
|
||||
event_loop: Some(event_loop),
|
||||
}
|
||||
}
|
||||
|
||||
impl MapWindowConfig for WinitMapWindowConfig {
|
||||
type MapWindow = WinitMapWindow;
|
||||
|
||||
fn create(&self) -> Self::MapWindow {
|
||||
let event_loop = WinitEventLoop::new();
|
||||
|
||||
let window: winit::window::Window = WindowBuilder::new()
|
||||
@ -23,21 +53,11 @@ impl MapWindow for WinitMapWindow {
|
||||
|
||||
let size = get_body_size().unwrap();
|
||||
window.set_inner_size(size);
|
||||
Self {
|
||||
Self::MapWindow {
|
||||
window,
|
||||
event_loop: Some(event_loop),
|
||||
}
|
||||
}
|
||||
|
||||
fn size(&self) -> WindowSize {
|
||||
let size = self.window.inner_size();
|
||||
|
||||
WindowSize::new(size.width, size.height).expect("failed to get window dimensions.")
|
||||
}
|
||||
|
||||
fn inner(&self) -> &Self::Window {
|
||||
&self.window
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_body_size() -> Option<winit::dpi::LogicalSize<i32>> {
|
||||
|
||||
@ -72,5 +72,7 @@ thiserror = "1"
|
||||
downcast-rs = "1.2"
|
||||
smallvec = "1.8"
|
||||
|
||||
png = "0.17"
|
||||
|
||||
[build-dependencies]
|
||||
maplibre-build-tools = { path = "../maplibre-build-tools", version = "0.1.0", features = ["sqlite"] }
|
||||
|
||||
@ -22,7 +22,7 @@ use crate::map_schedule::MapSchedule;
|
||||
use crate::render::settings::{RendererSettings, WgpuSettings};
|
||||
use crate::render::{RenderState, Renderer};
|
||||
use crate::style::Style;
|
||||
use crate::window::{MapWindow, MapWindowConfig, Runnable, WindowSize};
|
||||
use crate::window::{HasRawWindow, MapWindow, MapWindowConfig, Runnable, WindowSize};
|
||||
|
||||
pub mod context;
|
||||
pub mod coords;
|
||||
@ -47,9 +47,9 @@ pub(crate) mod tessellation;
|
||||
pub(crate) mod util;
|
||||
|
||||
/// Map's configuration and execution.
|
||||
pub struct Map<W, SM, HC>
|
||||
pub struct Map<MWC, SM, HC>
|
||||
where
|
||||
W: MapWindow,
|
||||
MWC: MapWindowConfig,
|
||||
SM: ScheduleMethod,
|
||||
HC: HTTPClient,
|
||||
{
|
||||
@ -57,9 +57,10 @@ where
|
||||
window: W,
|
||||
}
|
||||
|
||||
impl<W, SM, HC> Map<W, SM, HC>
|
||||
impl<MWC, SM, HC> Map<MWC, SM, HC>
|
||||
where
|
||||
W: MapWindow + Runnable<W::MapWindowConfig, SM, HC>,
|
||||
MWC: MapWindowConfig,
|
||||
MWC::MapWindow: Runnable<MWC, SM, HC>,
|
||||
SM: ScheduleMethod,
|
||||
HC: HTTPClient,
|
||||
{
|
||||
@ -142,6 +143,29 @@ where
|
||||
window,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn initialize_headless(self) -> Map<MWC, SM, HC> {
|
||||
let window = self.map_window_config.create();
|
||||
|
||||
let renderer = Renderer::initialize_headless::<MWC>(
|
||||
&window,
|
||||
self.wgpu_settings,
|
||||
self.renderer_settings,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
Map {
|
||||
map_state: MapSchedule::new(
|
||||
window.size(),
|
||||
self.map_window_config,
|
||||
Some(renderer),
|
||||
self.scheduler,
|
||||
self.http_client,
|
||||
self.style,
|
||||
),
|
||||
window,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MapBuilder<MWC, SM, HC>
|
||||
|
||||
65
maplibre/src/render/copy_surface_to_buffer_node.rs
Normal file
65
maplibre/src/render/copy_surface_to_buffer_node.rs
Normal file
@ -0,0 +1,65 @@
|
||||
use crate::render::graph::{Node, NodeRunError, RenderContext, RenderGraphContext, SlotInfo};
|
||||
use crate::render::render_commands::{DrawMasks, DrawTiles};
|
||||
use crate::render::render_phase::{PhaseItem, RenderCommand};
|
||||
use crate::render::resource::{Head, TrackedRenderPass};
|
||||
use crate::render::stages::draw_graph;
|
||||
use crate::render::util::FloatOrd;
|
||||
use crate::render::Eventually::Initialized;
|
||||
use crate::render::RenderState;
|
||||
use std::ops::{Deref, Range};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CopySurfaceBufferNode {}
|
||||
|
||||
impl CopySurfaceBufferNode {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for CopySurfaceBufferNode {
|
||||
fn input(&self) -> Vec<SlotInfo> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn update(&mut self, _state: &mut RenderState) {}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_graph: &mut RenderGraphContext,
|
||||
RenderContext {
|
||||
command_encoder, ..
|
||||
}: &mut RenderContext,
|
||||
state: &RenderState,
|
||||
) -> Result<(), NodeRunError> {
|
||||
match state.surface.head() {
|
||||
Head::Headed(_) => {}
|
||||
Head::Headless(buffered_texture) => {
|
||||
let size = state.surface.size();
|
||||
command_encoder.copy_texture_to_buffer(
|
||||
buffered_texture.texture.as_image_copy(),
|
||||
wgpu::ImageCopyBuffer {
|
||||
buffer: &buffered_texture.output_buffer,
|
||||
layout: wgpu::ImageDataLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(
|
||||
std::num::NonZeroU32::new(
|
||||
buffered_texture.buffer_dimensions.padded_bytes_per_row as u32,
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
rows_per_image: None,
|
||||
},
|
||||
},
|
||||
wgpu::Extent3d {
|
||||
width: size.width() as u32,
|
||||
height: size.height() as u32,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -27,10 +27,12 @@ use crate::render::shaders::{ShaderFeatureStyle, ShaderLayerMetadata};
|
||||
use crate::render::tile_view_pattern::{TileInView, TileShape, TileViewPattern};
|
||||
use crate::render::util::Eventually;
|
||||
use crate::tessellation::IndexDataType;
|
||||
use crate::MapWindow;
|
||||
use crate::{HasRawWindow, MapWindow, MapWindowConfig};
|
||||
use log::info;
|
||||
use std::sync::Arc;
|
||||
|
||||
// Rendering internals
|
||||
mod copy_surface_to_buffer_node;
|
||||
mod graph;
|
||||
mod graph_runner;
|
||||
mod main_pass;
|
||||
@ -52,7 +54,6 @@ pub use stages::register_render_stages;
|
||||
|
||||
pub const INDEX_FORMAT: wgpu::IndexFormat = wgpu::IndexFormat::Uint32; // Must match IndexDataType
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RenderState {
|
||||
render_target: Eventually<TextureView>,
|
||||
|
||||
@ -76,13 +77,33 @@ pub struct RenderState {
|
||||
depth_texture: Eventually<Texture>,
|
||||
multisampling_texture: Eventually<Option<Texture>>,
|
||||
|
||||
surface: Surface,
|
||||
|
||||
mask_phase: RenderPhase<TileInView>,
|
||||
tile_phase: RenderPhase<(IndexEntry, TileShape)>,
|
||||
}
|
||||
|
||||
impl RenderState {
|
||||
pub fn new(surface: Surface) -> Self {
|
||||
Self {
|
||||
render_target: Default::default(),
|
||||
buffer_pool: Default::default(),
|
||||
tile_view_pattern: Default::default(),
|
||||
tile_pipeline: Default::default(),
|
||||
mask_pipeline: Default::default(),
|
||||
globals_bind_group: Default::default(),
|
||||
depth_texture: Default::default(),
|
||||
multisampling_texture: Default::default(),
|
||||
surface,
|
||||
mask_phase: Default::default(),
|
||||
tile_phase: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Renderer {
|
||||
pub instance: wgpu::Instance,
|
||||
pub device: wgpu::Device,
|
||||
pub device: Arc<wgpu::Device>,
|
||||
pub queue: wgpu::Queue,
|
||||
pub adapter_info: wgpu::AdapterInfo,
|
||||
|
||||
@ -90,34 +111,29 @@ pub struct Renderer {
|
||||
pub settings: RendererSettings,
|
||||
|
||||
pub state: RenderState,
|
||||
pub surface: Surface,
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
/// Initializes the renderer by retrieving and preparing the GPU instance, device and queue
|
||||
/// for the specified backend.
|
||||
pub async fn initialize<MW>(
|
||||
window: &MW,
|
||||
pub async fn initialize<MWC>(
|
||||
window: &MWC::MapWindow,
|
||||
wgpu_settings: WgpuSettings,
|
||||
settings: RendererSettings,
|
||||
) -> Result<Self, wgpu::RequestDeviceError>
|
||||
where
|
||||
MW: MapWindow,
|
||||
MWC::MapWindow: HasRawWindow,
|
||||
MWC: MapWindowConfig,
|
||||
/* <<MWC as MapWindowConfig>::MapWindow as MapWindow>::Window:
|
||||
raw_window_handle::HasRawWindowHandle,*/
|
||||
{
|
||||
let instance = wgpu::Instance::new(wgpu_settings.backends.unwrap_or(wgpu::Backends::all()));
|
||||
|
||||
let maybe_surface = match &settings.surface_type {
|
||||
SurfaceType::Headless => None,
|
||||
SurfaceType::Headed => Some(Surface::from_window(&instance, window, &settings)),
|
||||
};
|
||||
let surface = Surface::from_window::<MWC>(&instance, window, &settings);
|
||||
|
||||
let compatible_surface = if let Some(surface) = &maybe_surface {
|
||||
match &surface.head() {
|
||||
Head::Headed(window_head) => Some(window_head.surface()),
|
||||
Head::Headless(_) => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
let compatible_surface = match &surface.head() {
|
||||
Head::Headed(window_head) => Some(window_head.surface()),
|
||||
Head::Headless(_) => None,
|
||||
};
|
||||
|
||||
let (device, queue, adapter_info) = Self::request_device(
|
||||
@ -131,11 +147,6 @@ impl Renderer {
|
||||
)
|
||||
.await?;
|
||||
|
||||
let surface = maybe_surface.unwrap_or_else(|| match &settings.surface_type {
|
||||
SurfaceType::Headless => Surface::from_image(&device, window, &settings),
|
||||
SurfaceType::Headed => Surface::from_window(&instance, window, &settings),
|
||||
});
|
||||
|
||||
match surface.head() {
|
||||
Head::Headed(window) => window.configure(&device),
|
||||
Head::Headless(_) => {}
|
||||
@ -143,18 +154,51 @@ impl Renderer {
|
||||
|
||||
Ok(Self {
|
||||
instance,
|
||||
device,
|
||||
device: Arc::new(device),
|
||||
queue,
|
||||
adapter_info,
|
||||
wgpu_settings,
|
||||
settings,
|
||||
state: Default::default(),
|
||||
surface,
|
||||
state: RenderState::new(surface),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn initialize_headless<MWC>(
|
||||
window: &MWC::MapWindow,
|
||||
wgpu_settings: WgpuSettings,
|
||||
settings: RendererSettings,
|
||||
) -> Result<Self, wgpu::RequestDeviceError>
|
||||
where
|
||||
MWC: MapWindowConfig,
|
||||
{
|
||||
let instance = wgpu::Instance::new(wgpu_settings.backends.unwrap_or(wgpu::Backends::all()));
|
||||
|
||||
let (device, queue, adapter_info) = Self::request_device(
|
||||
&instance,
|
||||
&wgpu_settings,
|
||||
&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu_settings.power_preference,
|
||||
force_fallback_adapter: false,
|
||||
compatible_surface: None,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
let surface = Surface::from_image::<MWC>(&device, window, &settings);
|
||||
|
||||
Ok(Self {
|
||||
instance,
|
||||
device: Arc::new(device),
|
||||
queue,
|
||||
adapter_info,
|
||||
wgpu_settings,
|
||||
settings,
|
||||
state: RenderState::new(surface),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, width: u32, height: u32) {
|
||||
self.surface.resize(width, height)
|
||||
self.state.surface.resize(width, height)
|
||||
}
|
||||
|
||||
/// Requests a device
|
||||
@ -323,7 +367,7 @@ impl Renderer {
|
||||
&self.state
|
||||
}
|
||||
pub fn surface(&self) -> &Surface {
|
||||
&self.surface
|
||||
&self.state.surface
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -4,14 +4,17 @@
|
||||
use crate::render::resource::texture::TextureView;
|
||||
use crate::render::settings::RendererSettings;
|
||||
use crate::render::util::HasChanged;
|
||||
use crate::{MapWindow, WindowSize};
|
||||
use crate::{HasRawWindow, MapWindow, MapWindowConfig, WindowSize};
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::mem::size_of;
|
||||
use std::sync::Arc;
|
||||
|
||||
struct BufferDimensions {
|
||||
width: usize,
|
||||
height: usize,
|
||||
unpadded_bytes_per_row: usize,
|
||||
padded_bytes_per_row: usize,
|
||||
pub struct BufferDimensions {
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub unpadded_bytes_per_row: usize,
|
||||
pub padded_bytes_per_row: usize,
|
||||
}
|
||||
|
||||
impl BufferDimensions {
|
||||
@ -40,9 +43,11 @@ impl WindowHead {
|
||||
self.surface.configure(device, &self.surface_config);
|
||||
}
|
||||
|
||||
pub fn recreate_surface<MW>(&mut self, window: &MW, instance: &wgpu::Instance)
|
||||
pub fn recreate_surface<MWC>(&mut self, window: &MWC::MapWindow, instance: &wgpu::Instance)
|
||||
where
|
||||
MW: MapWindow,
|
||||
MWC: MapWindowConfig,
|
||||
<<MWC as MapWindowConfig>::MapWindow as MapWindow>::RawWindow:
|
||||
raw_window_handle::HasRawWindowHandle,
|
||||
{
|
||||
self.surface = unsafe { instance.create_surface(window.inner()) };
|
||||
}
|
||||
@ -52,14 +57,61 @@ impl WindowHead {
|
||||
}
|
||||
|
||||
pub struct BufferedTextureHead {
|
||||
texture: wgpu::Texture,
|
||||
output_buffer: wgpu::Buffer,
|
||||
buffer_dimensions: BufferDimensions,
|
||||
pub texture: wgpu::Texture,
|
||||
pub output_buffer: wgpu::Buffer,
|
||||
pub buffer_dimensions: BufferDimensions,
|
||||
}
|
||||
|
||||
impl BufferedTextureHead {
|
||||
pub async fn create_png<'a>(
|
||||
&self,
|
||||
png_output_path: &str,
|
||||
// device: &wgpu::Device,
|
||||
) {
|
||||
// Note that we're not calling `.await` here.
|
||||
let buffer_slice = self.output_buffer.slice(..);
|
||||
let buffer_future = buffer_slice.map_async(wgpu::MapMode::Read);
|
||||
|
||||
// Poll the device in a blocking manner so that our future resolves.
|
||||
// In an actual application, `device.poll(...)` should
|
||||
// be called in an event loop or on another thread.
|
||||
//device.poll(wgpu::Maintain::Wait);
|
||||
if let Ok(()) = buffer_future.await {
|
||||
let padded_buffer = buffer_slice.get_mapped_range();
|
||||
|
||||
let mut png_encoder = png::Encoder::new(
|
||||
File::create(png_output_path).unwrap(),
|
||||
self.buffer_dimensions.width as u32,
|
||||
self.buffer_dimensions.height as u32,
|
||||
);
|
||||
png_encoder.set_depth(png::BitDepth::Eight);
|
||||
png_encoder.set_color(png::ColorType::Rgba);
|
||||
let mut png_writer = png_encoder
|
||||
.write_header()
|
||||
.unwrap()
|
||||
.into_stream_writer_with_size(self.buffer_dimensions.unpadded_bytes_per_row)
|
||||
.unwrap();
|
||||
|
||||
// from the padded_buffer we write just the unpadded bytes into the image
|
||||
for chunk in padded_buffer.chunks(self.buffer_dimensions.padded_bytes_per_row) {
|
||||
png_writer
|
||||
.write_all(&chunk[..self.buffer_dimensions.unpadded_bytes_per_row])
|
||||
.unwrap();
|
||||
}
|
||||
png_writer.finish().unwrap();
|
||||
|
||||
// With the current interface, we have to make sure all mapped views are
|
||||
// dropped before we unmap the buffer.
|
||||
drop(padded_buffer);
|
||||
|
||||
self.output_buffer.unmap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Head {
|
||||
Headed(WindowHead),
|
||||
Headless(BufferedTextureHead),
|
||||
Headless(Arc<BufferedTextureHead>),
|
||||
}
|
||||
|
||||
pub struct Surface {
|
||||
@ -68,13 +120,16 @@ pub struct Surface {
|
||||
}
|
||||
|
||||
impl Surface {
|
||||
pub fn from_window<MW>(
|
||||
pub fn from_window<MWC>(
|
||||
instance: &wgpu::Instance,
|
||||
window: &MW,
|
||||
window: &MWC::MapWindow,
|
||||
settings: &RendererSettings,
|
||||
) -> Self
|
||||
where
|
||||
MW: MapWindow,
|
||||
MWC: MapWindowConfig,
|
||||
MWC::MapWindow: HasRawWindow,
|
||||
/* <<MWC as MapWindowConfig>::MapWindow as MapWindow>::Window:
|
||||
raw_window_handle::HasRawWindowHandle,*/
|
||||
{
|
||||
let size = window.size();
|
||||
let surface_config = wgpu::SurfaceConfiguration {
|
||||
@ -86,7 +141,7 @@ impl Surface {
|
||||
present_mode: wgpu::PresentMode::Fifo, // VSync
|
||||
};
|
||||
|
||||
let surface = unsafe { instance.create_surface(window.inner()) };
|
||||
let surface = unsafe { instance.create_surface(window.raw_window()) };
|
||||
|
||||
Self {
|
||||
size,
|
||||
@ -97,9 +152,13 @@ impl Surface {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_image<MW>(device: &wgpu::Device, window: &MW, settings: &RendererSettings) -> Self
|
||||
pub fn from_image<MWC>(
|
||||
device: &wgpu::Device,
|
||||
window: &MWC::MapWindow,
|
||||
settings: &RendererSettings,
|
||||
) -> Self
|
||||
where
|
||||
MW: MapWindow,
|
||||
MWC: MapWindowConfig,
|
||||
{
|
||||
let size = window.size();
|
||||
|
||||
@ -111,7 +170,7 @@ impl Surface {
|
||||
BufferDimensions::new(size.width() as usize, size.height() as usize);
|
||||
// The output buffer lets us retrieve the data as an array
|
||||
let output_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: None,
|
||||
label: Some("BufferedTextureHead buffer"),
|
||||
size: (buffer_dimensions.padded_bytes_per_row * buffer_dimensions.height) as u64,
|
||||
usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
@ -133,11 +192,11 @@ impl Surface {
|
||||
|
||||
Self {
|
||||
size,
|
||||
head: Head::Headless(BufferedTextureHead {
|
||||
head: Head::Headless(Arc::new(BufferedTextureHead {
|
||||
texture,
|
||||
output_buffer,
|
||||
buffer_dimensions,
|
||||
}),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,7 +218,8 @@ impl Surface {
|
||||
};
|
||||
frame.into()
|
||||
}
|
||||
Head::Headless(BufferedTextureHead { texture, .. }) => texture
|
||||
Head::Headless(arc) => arc
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default())
|
||||
.into(),
|
||||
}
|
||||
@ -191,13 +251,17 @@ impl Surface {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recreate<MW>(&mut self, window: &MW, instance: &wgpu::Instance)
|
||||
pub fn recreate<MWC>(&mut self, window: &MWC::MapWindow, instance: &wgpu::Instance)
|
||||
where
|
||||
MW: MapWindow,
|
||||
MWC: MapWindowConfig,
|
||||
<<MWC as MapWindowConfig>::MapWindow as MapWindow>::RawWindow:
|
||||
raw_window_handle::HasRawWindowHandle,
|
||||
{
|
||||
match &mut self.head {
|
||||
Head::Headed(head) => {
|
||||
head.recreate_surface(window, instance);
|
||||
Head::Headed(window_head) => {
|
||||
if window_head.has_changed(&(self.size.width(), self.size.height())) {
|
||||
window_head.recreate_surface::<MWC>(window, instance);
|
||||
}
|
||||
}
|
||||
Head::Headless(_) => {}
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
// 3. "sub graph" modules should be nested beneath their parent graph module
|
||||
|
||||
use crate::context::MapContext;
|
||||
use crate::render::copy_surface_to_buffer_node::CopySurfaceBufferNode;
|
||||
use crate::render::graph::{EmptyNode, RenderGraph};
|
||||
use crate::render::graph_runner::RenderGraphRunner;
|
||||
use crate::render::main_pass::{MainPassDriverNode, MainPassNode};
|
||||
@ -24,6 +25,7 @@ pub mod draw_graph {
|
||||
pub mod input {}
|
||||
pub mod node {
|
||||
pub const MAIN_PASS: &str = "main_pass";
|
||||
pub const COPY: &str = "copy";
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,17 +36,20 @@ pub struct GraphRunnerStage {
|
||||
|
||||
impl Default for GraphRunnerStage {
|
||||
fn default() -> Self {
|
||||
let pass_node = MainPassNode::new();
|
||||
let mut graph = RenderGraph::default();
|
||||
|
||||
let mut draw_graph = RenderGraph::default();
|
||||
draw_graph.add_node(draw_graph::node::MAIN_PASS, pass_node);
|
||||
draw_graph.add_node(draw_graph::node::MAIN_PASS, MainPassNode::new());
|
||||
let input_node_id = draw_graph.set_input(vec![]);
|
||||
draw_graph
|
||||
.add_node_edge(input_node_id, draw_graph::node::MAIN_PASS)
|
||||
.unwrap();
|
||||
graph.add_sub_graph(draw_graph::NAME, draw_graph);
|
||||
draw_graph.add_node(draw_graph::node::COPY, CopySurfaceBufferNode::default());
|
||||
draw_graph
|
||||
.add_node_edge(draw_graph::node::MAIN_PASS, draw_graph::node::COPY)
|
||||
.unwrap();
|
||||
|
||||
graph.add_sub_graph(draw_graph::NAME, draw_graph);
|
||||
graph.add_node(node::MAIN_PASS_DEPENDENCIES, EmptyNode);
|
||||
graph.add_node(node::MAIN_PASS_DRIVER, MainPassDriverNode);
|
||||
graph
|
||||
|
||||
@ -11,10 +11,12 @@ mod phase_sort_stage;
|
||||
mod queue_stage;
|
||||
mod resource_stage;
|
||||
mod upload_stage;
|
||||
mod write_surface_buffer_stage;
|
||||
|
||||
use crate::multi_stage;
|
||||
use crate::render::stages::phase_sort_stage::PhaseSortStage;
|
||||
use crate::render::stages::queue_stage::QueueStage;
|
||||
use crate::render::stages::write_surface_buffer_stage::WriteSurfaceBufferStage;
|
||||
pub use graph_runner_stage::{draw_graph, node};
|
||||
|
||||
/// The labels of the default App rendering stages.
|
||||
@ -53,4 +55,8 @@ pub fn register_render_stages(schedule: &mut Schedule) {
|
||||
schedule.add_stage(RenderStageLabel::Queue, QueueStage::default());
|
||||
schedule.add_stage(RenderStageLabel::PhaseSort, PhaseSortStage::default());
|
||||
schedule.add_stage(RenderStageLabel::Render, GraphRunnerStage::default());
|
||||
schedule.add_stage(
|
||||
RenderStageLabel::Cleanup,
|
||||
WriteSurfaceBufferStage::default(),
|
||||
);
|
||||
}
|
||||
|
||||
@ -28,13 +28,14 @@ impl Stage for ResourceStage {
|
||||
Renderer {
|
||||
settings,
|
||||
device,
|
||||
surface,
|
||||
state,
|
||||
..
|
||||
},
|
||||
..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
let surface = &mut state.surface;
|
||||
|
||||
let size = surface.size();
|
||||
|
||||
surface.reconfigure(device);
|
||||
|
||||
@ -27,15 +27,7 @@ impl Stage for UploadStage {
|
||||
view_state,
|
||||
style,
|
||||
tile_cache,
|
||||
renderer:
|
||||
Renderer {
|
||||
settings: _,
|
||||
device: _,
|
||||
queue,
|
||||
surface: _,
|
||||
state,
|
||||
..
|
||||
},
|
||||
renderer: Renderer { queue, state, .. },
|
||||
..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
|
||||
50
maplibre/src/render/stages/write_surface_buffer_stage.rs
Normal file
50
maplibre/src/render/stages/write_surface_buffer_stage.rs
Normal file
@ -0,0 +1,50 @@
|
||||
//! Sorts items of the [RenderPhases](RenderPhase).
|
||||
|
||||
use crate::context::MapContext;
|
||||
use crate::coords::{ViewRegion, Zoom};
|
||||
use crate::io::tile_cache::TileCache;
|
||||
use crate::io::LayerTessellateMessage;
|
||||
use crate::render::camera::ViewProjection;
|
||||
use crate::render::render_phase::RenderPhase;
|
||||
use crate::render::resource::{BufferDimensions, BufferedTextureHead, Head, IndexEntry};
|
||||
use crate::render::shaders::{
|
||||
ShaderCamera, ShaderFeatureStyle, ShaderGlobals, ShaderLayerMetadata, Vec4f32,
|
||||
};
|
||||
use crate::render::tile_view_pattern::TileInView;
|
||||
use crate::render::util::Eventually::Initialized;
|
||||
use crate::schedule::Stage;
|
||||
use crate::{RenderState, Renderer, Style};
|
||||
use std::fs::File;
|
||||
use std::future::Future;
|
||||
use std::io::Write;
|
||||
use std::iter;
|
||||
use std::ops::Deref;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::task;
|
||||
use wgpu::{BufferAsyncError, BufferSlice};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct WriteSurfaceBufferStage;
|
||||
|
||||
impl Stage for WriteSurfaceBufferStage {
|
||||
fn run(
|
||||
&mut self,
|
||||
MapContext {
|
||||
renderer: Renderer { state, .. },
|
||||
..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
match state.surface.head() {
|
||||
Head::Headed(_) => {}
|
||||
Head::Headless(buffered_texture) => {
|
||||
let buffered_texture = buffered_texture.clone();
|
||||
|
||||
task::block_in_place(|| {
|
||||
Handle::current().block_on(async {
|
||||
buffered_texture.create_png("test.png").await;
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,22 +1,40 @@
|
||||
//! Utilities for the window system.
|
||||
|
||||
use crate::{HTTPClient, MapSchedule, ScheduleMethod};
|
||||
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
|
||||
|
||||
/// Window with an optional [carte::window::WindowSize].
|
||||
pub trait MapWindow {
|
||||
type EventLoop;
|
||||
type Window: raw_window_handle::HasRawWindowHandle; // FIXME: Not true for headless
|
||||
type MapWindowConfig: MapWindowConfig<MapWindow = Self>;
|
||||
|
||||
fn create(map_window_config: &Self::MapWindowConfig) -> Self;
|
||||
type RawWindow;
|
||||
|
||||
fn size(&self) -> WindowSize;
|
||||
|
||||
fn inner(&self) -> &Self::Window;
|
||||
fn inner(&self) -> &Self::RawWindow;
|
||||
}
|
||||
|
||||
pub trait HasRawWindow {
|
||||
type HRWH: HasRawWindowHandle;
|
||||
|
||||
fn raw_window(&self) -> &Self::HRWH;
|
||||
}
|
||||
|
||||
impl<MW> HasRawWindow for MW
|
||||
where
|
||||
MW: MapWindow,
|
||||
MW::RawWindow: HasRawWindowHandle,
|
||||
{
|
||||
type HRWH = MW::RawWindow;
|
||||
|
||||
fn raw_window(&self) -> &Self::HRWH {
|
||||
self.inner()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MapWindowConfig: 'static {
|
||||
type MapWindow: MapWindow<MapWindowConfig = Self>;
|
||||
type MapWindow: MapWindow;
|
||||
|
||||
fn create(&self) -> Self::MapWindow;
|
||||
}
|
||||
|
||||
pub trait Runnable<MWC, SM, HC>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user