Pull to refresh
9
@lain8donoread⁠-⁠only

Rust Evangelist

Send message

Чисто технически все реализации WebGPU суть обёртки над существующими GAPI. Да ещё и реализации сделаны таким образом, что их вполне можно использовать вне браузерной тусовки. И получилось так хорошо, что это всё имеет смысл использовать в отрыве от браузеров.


При этом всём только команда от Mozilla (wgpu) ставит своей целью реализацию поверх всех GAPI, с которыми это вообще имеет смысл.



На текущий момент wgpu-rs уже достаточно хорош для использования в небольших проектах.


разрабатывается с фокусом на браузеры

Для тех, кто использует wgpu-rs, это означает наличие браузеров в качестве ещё одной платформы. Когда-нибудь даже и не в ночных сборках.


В любом случае для вас это скорее хорошая новость. В конце концов браузерам требуется кросс-платформенность, а это ваша цель.

Надеюсь WebGPU будет существенно проще.

Пример с compute там рядом лежит. https://github.com/gfx-rs/wgpu-rs/tree/master/examples/hello-compute

Не знаю на счёт Khronos и какое они отношение имеют к WebGPU.


началось всё с попытки Apple

Кстати по какой-то причине редактор со стороны Apple теперь бывший:



И вроде как это всё не совсем с Apple началось. По крайней мере суть в том, что все основные заинтересованные (Mozilla, Google, Apple) в какой-то момент собрались и решили делать новый GAPI ибо поняли, что WebGL это путь вникуда.


и на что же он на самом деле похож

На самом деле я достаточно плохо знаком с другими GAPI. Но точно уверен, что по чуть-чуть оно похоже на все современные GAPI. И в тоже время это полностью независимый GAPI. Однако несколько моментов всёж отмечу.


WebGPU очень простой для пользователя. Вероятно это самый простой GAPI на сегодняшний день. Определённо проще чем Vulkan/DX12/Metal. И даже проще чем OpenGL (ES). Вот пример классического треугольника:


main.rs
use std::borrow::Cow;
use winit::{
    event::{Event, WindowEvent},
    event_loop::{ControlFlow, EventLoop},
    window::Window,
};

async fn run(event_loop: EventLoop<()>, window: Window) {
    let size = window.inner_size();
    let instance = wgpu::Instance::new(wgpu::BackendBit::all());
    let surface = unsafe { instance.create_surface(&window) };
    let adapter = instance
        .request_adapter(&wgpu::RequestAdapterOptions {
            power_preference: wgpu::PowerPreference::default(),
            // Request an adapter which can render to our surface
            compatible_surface: Some(&surface),
        })
        .await
        .expect("Failed to find an appropriate adapter");

    // Create the logical device and command queue
    let (device, queue) = adapter
        .request_device(
            &wgpu::DeviceDescriptor {
                label: None,
                features: wgpu::Features::empty(),
                limits: wgpu::Limits::default(),
            },
            None,
        )
        .await
        .expect("Failed to create device");

    // Load the shaders from disk
    let shader = device.create_shader_module(&wgpu::ShaderModuleDescriptor {
        label: None,
        source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))),
        flags: wgpu::ShaderFlags::all(),
    });

    let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
        label: None,
        bind_group_layouts: &[],
        push_constant_ranges: &[],
    });

    let swapchain_format = adapter.get_swap_chain_preferred_format(&surface).unwrap();

    let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
        label: None,
        layout: Some(&pipeline_layout),
        vertex: wgpu::VertexState {
            module: &shader,
            entry_point: "vs_main",
            buffers: &[],
        },
        fragment: Some(wgpu::FragmentState {
            module: &shader,
            entry_point: "fs_main",
            targets: &[swapchain_format.into()],
        }),
        primitive: wgpu::PrimitiveState::default(),
        depth_stencil: None,
        multisample: wgpu::MultisampleState::default(),
    });

    let mut sc_desc = wgpu::SwapChainDescriptor {
        usage: wgpu::TextureUsage::RENDER_ATTACHMENT,
        format: swapchain_format,
        width: size.width,
        height: size.height,
        present_mode: wgpu::PresentMode::Mailbox,
    };

    let mut swap_chain = device.create_swap_chain(&surface, &sc_desc);

    event_loop.run(move |event, _, control_flow| {
        // Have the closure take ownership of the resources.
        // `event_loop.run` never returns, therefore we must do this to ensure
        // the resources are properly cleaned up.
        let _ = (&instance, &adapter, &shader, &pipeline_layout);

        *control_flow = ControlFlow::Poll;
        match event {
            Event::WindowEvent {
                event: WindowEvent::Resized(size),
                ..
            } => {
                // Recreate the swap chain with the new size
                sc_desc.width = size.width;
                sc_desc.height = size.height;
                swap_chain = device.create_swap_chain(&surface, &sc_desc);
            }
            Event::RedrawRequested(_) => {
                let frame = swap_chain
                    .get_current_frame()
                    .expect("Failed to acquire next swap chain texture")
                    .output;
                let mut encoder =
                    device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
                {
                    let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
                        label: None,
                        color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
                            attachment: &frame.view,
                            resolve_target: None,
                            ops: wgpu::Operations {
                                load: wgpu::LoadOp::Clear(wgpu::Color::GREEN),
                                store: true,
                            },
                        }],
                        depth_stencil_attachment: None,
                    });
                    rpass.set_pipeline(&render_pipeline);
                    rpass.draw(0..3, 0..1);
                }

                queue.submit(Some(encoder.finish()));
            }
            Event::WindowEvent {
                event: WindowEvent::CloseRequested,
                ..
            } => *control_flow = ControlFlow::Exit,
            _ => {}
        }
    });
}

fn main() {
    let event_loop = EventLoop::new();
    let window = winit::window::Window::new(&event_loop).unwrap();
    #[cfg(not(target_arch = "wasm32"))]
    {
        wgpu_subscriber::initialize_default_subscriber(None);
        // Temporarily avoid srgb formats for the swapchain on the web
        pollster::block_on(run(event_loop, window));
    }
    #[cfg(target_arch = "wasm32")]
    {
        std::panic::set_hook(Box::new(console_error_panic_hook::hook));
        console_log::init().expect("could not initialize logger");
        use winit::platform::web::WindowExtWebSys;
        // On wasm, append the canvas to the document body
        web_sys::window()
            .and_then(|win| win.document())
            .and_then(|doc| doc.body())
            .and_then(|body| {
                body.append_child(&web_sys::Element::from(window.canvas()))
                    .ok()
            })
            .expect("couldn't append canvas to document body");
        wasm_bindgen_futures::spawn_local(run(event_loop, window));
    }
}

shader.wgsl
[[stage(vertex)]]
fn vs_main([[builtin(vertex_index)]] in_vertex_index: u32) -> [[builtin(position)]] vec4<f32> {
    var x: f32 = f32(i32(in_vertex_index) - 1);
    var y: f32 = f32(i32(in_vertex_index & 1) * 2 - 1);
    return vec4<f32>(x, y, 0.0, 1.0);
}

[[stage(fragment)]]
fn fs_main() -> [[location(0)]] vec4<f32> {
    return vec4<f32>(1.0, 0.0, 0.0, 1.0);
}

Тут примерно следующее:


  1. Для начала получаем два основных элемента, которые в дальнейшем будут использоваться. Это Device и Queue. Девайс нам нужен для создания всяких полезностей вроде пайплайнов, текстур, буферов и прочего. Очередь для выполнения команд на GPU.
  2. Создаём RenderPipeline скармливая RenderPipelineDescriptor нашему девайсу (device). Это то как мы рисуем и какие ресурсы для этого используем. Что-то вроде набора настоек рисования. Если нам нужно что-то вычислять, то берём ComputePipeline и ComputePipelineDescriptor соответсвенно.
  3. Создаём SwapChain. В некотором смысле эта часть опциональна. В конце концов мы можем использовать WebGPU не для графики, а для рассчётов. (Кстати логично же, что этапы 2 и 3 можно менять местами)
  4. Собсвенно цикл рисования. Создаём CommandEncoder и скармливаем ему всякие команды рисования/вычислений и прочего. Как только закончим, то превращяем энкодер (encoder) в CommandBuffer и отправляем в нашу очередь (queue).

Да в общем-то и всё. Остальное детали. А, ну про WGSL ещё. Не смотря на очевидную сырость и некоторые недоработки (я сам сделал пару коммитов в naga для поддержки парочки базовых вещей) этот язык ВОСХИТИТЕЛЕН. Особенно вот эта штука, где делаешь структурку типа VertexOutput и используешь её одновременно в вертексной и фрагментной части (пример). А, ну да, они же ещё и в одном файле!


Кстати эту штуку со структурками сделали буквально пару дней назад.


Если хочется потыкать это всё прям сейчас, то подучите Rust и добро пожаловать в нашу уютную конфу. Серьёзно, wgpu-rs уже вполне можно считать достаточно юзабельным. Другое дело, что как раз таки в браузере оно не работает (не считая ночных сборок и вот этого всего). Кстати в настоящий момент есть как минимум два инересных проекта. Игровой движок bevy и реализация графического интерфейса iced. Ну ладно, они сами по себе местами сырые. Да и вообще с моей точки зрения у них есть один маленький, но фатальный недостаток. Но об этом как-нибудь в в другой раз. Возможно сделаю парочку статей на эту тему, но ничего не обещаю.

а разработчики web-стандартов готовят WebGPU

Кому интересно: спецификация, issues.


При этом реализацию от Mozilla вполне можно пощупать нативно: wgpu-rs.

… а вот брат – неизвестно.

Но и в этом случае нас в первую очередь интересует, что из него выпало.

Зашёл сюда за этим комментарием.

Есть мнение, что это про околоминимальную зп и дешёвый интернет. Для России можно прикинуть что-то такое:


Возьмём зп 12к в месяц. Пусть у нас будет 21 рабочий день и соответственно 168 часов рабочих. В месяц. Получается чуть более 70 рублей в час. При цене 100 рублей за месяц как раз и получается 1.4 часа на работу для обеспечения этого месяца.


Формула: (12000 / 168) * 1.4 == 100.


Понятное дело, что расчёты приблизительные.

Какое у нас непредсказуемое прошлое.

А как на счёт тех, у кого телефона нет? Тем наверное чип в попу и штрихкод на шею.

Valve напомнила, что является частной компанией, в отличие от Samsung.

А можно подробнее? Что такое частная компания и почему Samsung ей не является? Вопрос глупый, понимаю. Но именно поэтому стоит его задать.

Это только транспортный протокол.

Это один из фатальных недостатков.

Предлагаю не ставить это вообще. Из соседней новости: https://habr.com/ru/news/t/541638/



Да, оно может читать ваши данные и не только.

А ещё в возможности свернуть их. На моём первом скриншоте около 150 вкладок из 820, часть свёрнуты.


Впрочем вот
Firefox + TreeStyleTabs на Arch Linux

Это для сравнения. Так выглядит повседневное использование боковой панели с вкладками.

Information

Rating
Does not participate
Location
Москва, Москва и Московская обл., Россия
Registered
Activity