Pull to refresh

Comments 17

И если пользователи Safari на macOS могут отделаться лёгким шоком и установить браузер посовременнее, то пользователи iOS такой возможности лишены политикой Apple.

Это, оказывается, WebKit… Как и firefox и всё остальное.
apps.apple.com/ru/app/chrome-браузер-от-google/id535886823#?platform=iphone
Честно, не знал до этой статьи.
По той ссылке из статьи надо аккуратно прочитать ответ ;):
all Chrome variants except iOS now use Blink

То есть современный Chrome сейчас базируется на собственном форке движка WebKit, который развивается независимо. Поэтому Chrome на macOS нормально выдаёт WebGL 2.0.
Но это НЕ касается версии Chrome для iOS.
Уже прочитал :)
Вопросы правда не по теме появились: например, как так получилось, что даже MS когда-то обязали делать выбор браузера ( habr.com/ru/post/85925 ), а Apple по сути практически запрещает ПО такого типа, разрешая только обёртку.
а Apple по сути практически запрещает ПО такого типа, разрешая только обёртку.

Потому что Apple двигает Metal. Сейчас повторяется ситуация DirectX vs OpenGL пятнадцатилетней давности.
Как раз сейчас пытаются запускать подобные процессы (вроде "Epic Games против Apple", но там дело больше в жадности, нежели в борьбе за справедливость и свободы пользователей), но, очевидно, с очень большим запозданием.

Искусственные ограничения конкуренции и свобод пользователей были очевидны с самого запуска iPhone, и антимонопольные структуры должны были начать давить на Apple как только стало понятно, что их продукты стали массовыми.

Но тут есть и другие неудачные прецеденты, вроде тех же игровых приставок от Sony и Microsoft, таких же закрытых и навязывающих свои условия — практику, которую они ввели задолго до появления iOS…

Это шок, до сегодняшнего дня я был уверен что WebGl2 на iphone будет работать в хроме. Но сейчас я как будто упал с небес на землю. Как же так?? Может я все же, что то не правильно понял?

Да, всё именно так — достаточно зайти на айфоне на WebGL Report страничку.
Думаю, если бы на iOS были бы альтернативы, то можно было бы и забросить эту затею с мучениями на WebGL 1.0, описанными в статье…

Хотя есть некоторые причины ожидать, что одной проблемой станет меньше с очередным крупным обновлением iOS.
В обсуждениях Khronos упоминали, что первые WebGPU спецификации обещают закрыть к концу этого года. Сама история WebGPU достаточно любопытна — началось всё с попытки Apple выдвинуть его в виде WebMetal, очевидно на основе своего Metal, но коллеги не оценили и стали переписывать его в сторону Vulkan и с другим названием. При этом WebGPU не является калькой вулкана, а чем-то новым…

Поэтому было бы интересно, чтобы тот, кто варится в этих экспериментах WebGPU, рассказал какие очертания этот новый стандарт принимает на текущий момент, и на что же он на самом деле похож.

Не знаю на счёт 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. Ну ладно, они сами по себе местами сырые. Да и вообще с моей точки зрения у них есть один маленький, но фатальный недостаток. Но об этом как-нибудь в в другой раз. Возможно сделаю парочку статей на эту тему, но ничего не обещаю.

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

Да, всё время забываю, что для разработки web-стандартов есть свои консорциумы.
Поэтому WebXR получился непохожим на OpenXR, хотя одно и реализуется поверх другого и решает по сути те же самые задачи…
При этом WebGPU не является калькой вулкана, а чем-то новым…

Что очень хорошо. У меня нет большого опыта работы с разными графическими API, но недавно решил попробовать написать compute shader для своих задач. 30 вызовов API, 15 структур данных. Всё чертовски сложно друг с другом переплетено. Я, честно, не смог смог исправить готовое демо так, чтобы сделать от, что мне нужно. Надеюсь WebGPU будет существенно проще.

Я не против нового графического API, дружелюбного к разработчикам и способного выжать максимум производительности из современного графического процессора. Vulkan определённо переусложнён, а OpenGL тянет шлейф старых решений (в этом его и сила и слабость).

Но ведь WebGPU — это не новый графический API для всех, он разрабатывается с фокусом на браузеры (хотя по комментарию lain8dono у меня сложилось ощущение, что это не совсем так).

А мне, как разработчику кросс-платформенных приложений от появления ещё одного Direct3D, Metal, Mantle для конкретной платформы вместо кросс-платформенного стандарта или адаптации оного (как WebGL близок к OpenGL ES) никак не поможет.

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


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



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


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

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


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

Вот только что насчет качества реализации? Пробую сделать AR модель для андроид и иос, на последней все отлично, а на андроид нет. Вот так получается: модель под android, ios, desktop chrome
Тут просто беда с прозрачностью замкнутой поверхности (резервуар) — не разобрать, где скважины внутри и где снаружи, а если делаю полупрозрачной не замкнутую поверхность (рельеф), эта поверхность начинает бликовать, хотя металлический эффект полностью отключен. Или я что-то не так делаю, или реализация от гугла не рабочая для всего, кроме интернет магазинов...

Баги — это отдельная тема. Что показывает конкретная демка не могу судить — у меня она ведёт себя одинаково на десктопе (Firefox) и телефоне (Android), поэтому скорее всего что-то не так с реализацией. Выглядит словно кто-то забыл отключить запись буфера глубины при отрисовке полупрозрачных объектов.

На Android разработчиков GPU прорва и у каждого свои драйвера со своими багами. Первые реализации OpenGL ES 3.0 на Android были жутко глючными, но со временем подтянули уровень. А на iOS — один вендор и только одно поведение.

Разумеется, чем меньше разнообразия, тем проще вести разработку (баги / не баги — не так важно, когда всё знакомо и ожидаемо, то и баги могут стать родными). Но и общаться с одним единственным вендором сложнее — он начинает диктовать свои условия, а замечания, что у него что-то не так просто игнорировать.
Sign up to leave a comment.

Articles