chromiumembedded / cef

Chromium Embedded Framework (CEF). A simple framework for embedding Chromium-based browsers in other applications.
https://bitbucket.org/chromiumembedded/cef/
Other
3.38k stars 467 forks source link

. #3755

Closed Scalibq closed 3 months ago

reitowo commented 3 months ago

Please note that OnAcceleratedPaint behavior changes a lot since CEF 124. It no longer accepts an external texture, and it gives out sequential textures owned by chromium. It will be recycled once the callback returns.

You have to copy the texture to your own texture buffer before the callback function returns.

https://github.com/Scalibq/cef-mixer/blob/29e4358e8dd4b7705c817be328f270162c677ac0/src/web_layer.cpp#L326

From you code I think you just opened the handle, but not copy it before callback returns. So, it will be junky when you access it later cause next frames may writing to the same resource by chromium.

reitowo commented 3 months ago

You can share the code if you want. I can't reproduce it locally. It could be related to the logic you handling locks or something else with vsync / swapchain.

CEF creates its own D3D instance

That's not true. CEF doesn't create D3D in OSR. It passes whatever chromium gives to you, and you just can't pass a D3D instance to chromium.

reitowo commented 3 months ago

This seems especially bad when the application is in fullscreen mode. Create a fullscreen D3D application.

I'm just wondering why you can conclude this is a CEF bug despite all rendering code are implemented by yourself.

It has to contend with the D3D object that is rendering to screen. The D3D instances might be starving each other for resources.

Where does this come from? Once you opened the handle and copied to your own using your own device and context, it have nothing to do with that.

reitowo commented 3 months ago

Since you are very confident, you could just give a reproducable code as you're the issue creator.

Not to blame, but I don't quite understand why you post a link to your fork, but then say it was not your code and being aggresive. Where else do you expect me to verify the problem?

reitowo commented 3 months ago

There're cefclient target to test OSR function with args:

"args": [
  "--off-screen-rendering-enabled",
  "--shared-texture-enabled",
  "--transparent-painting-enabled",
  "--enable-chrome-runtime",
  "--url=https://www.youtube.com/watch?v=Cyxixzi2dgQ"
]

This doesn't use fullscreen, but vsync is enabled for that swapchain by default. And it can play the video smoothly.

image

I also tried to add a fullscreen code from your fork. The video first shows the smooth video in the original cefclient window, later is a fullscreen exclusive (when the clock looks stretched) footage. Both seems smooth to me.

https://github.com/user-attachments/assets/1d726645-a4f0-4cb4-a89b-10421e18afcb

Here's what I done:

  1. Use m127 from https://cef-builds.spotifycdn.com/index.html
  2. Create a new Window and a new Fullscreen swapchain

    // Create a D3D11 swapchain for the window.
    swap_chain_ = device_->create_swapchain(hwnd());
    DCHECK(swap_chain_);
    if (!swap_chain_) {
    return false;
    }
    
    // Create a second swap chain
    auto hwnd_fs = ::CreateWindowEx(
    0, L"Client_OsrWindow", nullptr,
    WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE,
    100 ,100, 1920,1080,
    nullptr, nullptr, nullptr, nullptr);
    CHECK(hwnd_fs);
    swap_chain_full_ = device_->create_swapchain(hwnd_fs, 0, 0, true);
    
    // Create the browser layer.
    browser_layer_ = std::make_shared<BrowserLayer>(device_);
    DXGI_SWAP_CHAIN_FULLSCREEN_DESC fsd = { 0 };
    if (fullscreen)
    {
        fsd.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_PROGRESSIVE;
        fsd.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
        fsd.Windowed = true;
    }
    
    IDXGISwapChain1* swapchain1 = nullptr;
    hr = dxgi_factory2->CreateSwapChainForHwnd(device_.get(), window, &sd,
                                               fullscreen ? &fsd : nullptr, nullptr, &swapchain1);
    if (SUCCEEDED(hr)) {
      hr = swapchain1->QueryInterface(__uuidof(IDXGISwapChain),
                                      reinterpret_cast<void**>(&swapchain));
      swapchain1->Release();
    }
  3. Listen Alt+Enter manually, and SetFullscreenState(true) to the new swapchain.
    if (message == WM_SYSKEYDOWN && event.modifiers & EVENTFLAG_ALT_DOWN && IsKeyDown(VK_RETURN))
    {
      render_handler_->SetFullscreenState(true);
    }
    void OsrRenderHandlerWinD3D11::SetFullscreenState(bool fullscreen)
    {
    swap_chain_full_->fullscreen(fullscreen);
    }
    void SwapChain::fullscreen(bool fullscreen) {
    swapchain_->SetFullscreenState(fullscreen, nullptr);
    }
  4. Draw to that swapchain, and Present with VSync.

    
    // Present to window.
    swap_chain_->present(send_begin_frame() ? 0 : 1);
    
    // Draw fullscreen
    swap_chain_full_->bind(ctx);  
    swap_chain_full_->resize(texture_size.first, texture_size.second);
    swap_chain_full_->clear(0.0f, 0.0f, 1.0f, 1.0f);
    composition_->render(ctx);
    swap_chain_full_->present(1);


I don't know if this can fully reproduce what your application is, the difference might be your application is heavier or the browser's texture is not rendered like this.
reitowo commented 3 months ago

If I create a separate thread and tick the Present there at 60fps, will it be same with your senario?

KubaGluszkiewicz commented 3 months ago

@Scalibq I'm using OnAccelerated path in 2d engine fully baked by D3D11 pipeline with preserving VSYNC which is always bounded to display refresh rate. My animation quality is butter smooth no single jerkiness.

My guess is, you are missing asynchronous nature of any gpu framework. All commands pushed to gpu pipeline are executed asynchronously till Present will not be called when those all operations needs to complete before swapping backbuffers etc etc...

So in your case if in OnAcceleratedPaint method you are not Presenting because your scenario is more advanced you need to assure that SharedHandle copy was completed before you will exit from OnAcceleratedCallback. It must be done, because you are accessing shared resources between devices, otherwise you will observe black flickering because when you are presenting shared texture copy may not be completed.

So double check if you don't have d3dCtx->Flush() call after copying shared resource. Because it guarantees that operation is completed. If you have a flush call you are stalling all gpu command buffer till everything will be completed. And you are loosing parallel computation benefit. Also if you have this Flush call jerkiness is guaranteed and this is source of your problem.

Instead of flushing context you need to inject query event and wait in your OnAcceleratedPaint till this event will be completed. That way you will avoid jerkiness and copy operation will be completed.

@reitowo once more thank you for your work on this. Excellent job mate!

reitowo commented 3 months ago

Also, it wouldn't explain why I could also reproduce the issue with the modified cef-mixer, which doesn't even copy the textures at all, it just creates an SRV directly on the shared texture. So in that case, there is no asynchronous issue with copying not being completed.

If you mean your fork, it doesn't even copy, so chromium writes next frame to it and makes it jerky. If you don't Flush the context and let the callback return, the async rendering would also happen after chromium reuse that resource.

Again, the best way works is

  1. open handle with your device
  2. get texture out of it
  3. copy to your texture or just render it
  4. flush your context
  5. and then return from the callback
reitowo commented 3 months ago

wow, never seen any people being so ignorant and impossible to communicate with, as you wish and have fun with your marvelous code. 😆 @Scalibq

KubaGluszkiewicz commented 3 months ago

There is a literally 0 chance anyone will jump here to help you solve it, considering your communication culture.

My last advice and take it seriously. I watched your issue on YT. It's not a problem with CEF/Chromium it's 100% problem in your code. Learn, figure out, improve and fix it.