#pragma comment(lib, "dxgi.lib") #pragma comment(lib, "d3d11.lib") #include "polyvox_filament_plugin.h" // This must be included before many other Windows headers. #include // For getPlatformVersion; remove unless needed for your plugin implementation. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "PolyvoxFilamentApi.h" #ifdef USE_ANGLE #include "PlatformANGLE.h" #endif #include #include using namespace std::chrono_literals; namespace polyvox_filament { // static void PolyvoxFilamentPlugin::RegisterWithRegistrar( flutter::PluginRegistrarWindows *registrar) { auto channel = std::make_unique>( registrar->messenger(), "app.polyvox.filament/event", &flutter::StandardMethodCodec::GetInstance()); auto plugin = std::make_unique( registrar->texture_registrar(), registrar); channel->SetMethodCallHandler( [plugin_pointer = plugin.get()](const auto &call, auto result) { plugin_pointer->HandleMethodCall(call, std::move(result)); }); registrar->AddPlugin(std::move(plugin)); } PolyvoxFilamentPlugin::PolyvoxFilamentPlugin( flutter::TextureRegistrar *textureRegistrar, flutter::PluginRegistrarWindows *pluginRegistrar) : _textureRegistrar(textureRegistrar), _pluginRegistrar(pluginRegistrar) {} PolyvoxFilamentPlugin::~PolyvoxFilamentPlugin() {} ResourceBuffer PolyvoxFilamentPlugin::loadResource(const char *name) { std::string name_str(name); std::filesystem::path targetFilePath; if (name_str.rfind("file://", 0) == 0) { targetFilePath = name_str.substr(7); } else { if (name_str.rfind("asset://", 0) == 0) { name_str = name_str.substr(8); } TCHAR pBuf[512]; size_t len = sizeof(pBuf); int bytes = GetModuleFileName(NULL, pBuf, len); std::wstring_convert> converter; std::wstring assetPath = converter.from_bytes(name_str.c_str()); std::wstring exePathBuf(pBuf); std::filesystem::path exePath(exePathBuf); auto exeDir = exePath.remove_filename(); targetFilePath = exeDir.wstring() + L"data/flutter_assets/" + assetPath; } std::streampos length; std::ifstream is(targetFilePath.c_str(), std::ios::binary); if (!is) { std::cout << "Failed to find resource at file path " << targetFilePath << std::endl; return ResourceBuffer(nullptr, 0, -1); } is.seekg(0, std::ios::end); length = is.tellg(); char *buffer; buffer = new char[length]; is.seekg(0, std::ios::beg); is.read(buffer, length); is.close(); auto id = _resources.size(); auto rb = ResourceBuffer(buffer, length, id); _resources.emplace(id, rb); std::wcout << "Loaded resource of length " << length << " from path " << targetFilePath << std::endl; return rb; } void PolyvoxFilamentPlugin::freeResource(ResourceBuffer rbuf) { free((void *)rbuf.data); } static ResourceBuffer _loadResource(const char *path, void *const plugin) { return ((PolyvoxFilamentPlugin *)plugin)->loadResource(path); } static void _freeResource(ResourceBuffer rbf, void *const plugin) { ((PolyvoxFilamentPlugin *)plugin)->freeResource(rbf); } // this is the C-style function that will be returned via getRenderCallback // called on every frame by the FFI API // this is just a convenient wrapper to call RenderCallback on the actual plugin instance void render_callback(void* owner) { ((PolyvoxFilamentPlugin*)owner)->RenderCallback(); } // this is the method on PolyvoxFilamentPlugin that will copy between D3D textures void PolyvoxFilamentPlugin::RenderCallback() { // std::lock_guard guard(*(_renderMutex.get())); if (_active) { #ifdef USE_ANGLE _active->RenderCallback(); #endif _textureRegistrar->MarkTextureFrameAvailable(_active->flutterTextureId); } } #ifdef USE_ANGLE bool PolyvoxFilamentPlugin::CreateSharedEGLContext() { //platform = new filament::backend::PlatformANGLE(_internalD3DTextureHandle, // width, height); _platform = new filament::backend::PlatformEGL(); // D3D starts here IDXGIAdapter* adapter_ = nullptr; // first, we need to initialize the D3D device and create the backing texture // this has been taken from https://github.com/alexmercerind/flutter-windows-ANGLE-OpenGL-ES/blob/master/windows/angle_surface_manager.cc auto feature_levels = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_3, }; // NOTE: Not enabling DirectX 12. // |D3D11CreateDevice| crashes directly on Windows 7. // D3D_FEATURE_LEVEL_12_2, D3D_FEATURE_LEVEL_12_1, D3D_FEATURE_LEVEL_12_0, // D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, // D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_3, IDXGIFactory* dxgi = nullptr; ::CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&dxgi); // Manually selecting adapter. As far as my experience goes, this is the // safest approach. Passing NULL (so-called default) seems to cause issues // on Windows 7 or maybe some older graphics drivers. // First adapter is the default. // |D3D_DRIVER_TYPE_UNKNOWN| must be passed with manual adapter selection. dxgi->EnumAdapters(0, &adapter_); dxgi->Release(); if (!adapter_) { std::cout << "Failed to locate default D3D adapter"<< std::endl; return false; } DXGI_ADAPTER_DESC adapter_desc_; adapter_->GetDesc(&adapter_desc_); std::wcout << L"D3D adapter description: " << adapter_desc_.Description << std::endl; auto hr = ::D3D11CreateDevice( adapter_, D3D_DRIVER_TYPE_UNKNOWN, 0, 0, feature_levels.begin(), static_cast(feature_levels.size()), D3D11_SDK_VERSION, &_D3D11Device, 0, &_D3D11DeviceContext); if (FAILED(hr)) { std::cout << "Failed to create D3D device"<< std::endl; return false; } Microsoft::WRL::ComPtr dxgi_device = nullptr; auto dxgi_device_success = _D3D11Device->QueryInterface( __uuidof(IDXGIDevice), (void**)&dxgi_device); if (SUCCEEDED(dxgi_device_success) && dxgi_device != nullptr) { dxgi_device->SetGPUThreadPriority(5); // Must be in interval [-7, 7]. } auto level = _D3D11Device->GetFeatureLevel(); std::cout << "media_kit: ANGLESurfaceManager: Direct3D Feature Level: " << (((unsigned)level) >> 12) << "_" << ((((unsigned)level) >> 8) & 0xf) << std::endl; // ******************* // * * // * * // * EGL starts here * // * * // * * // * * // ******************* EGLBoolean bindAPI = eglBindAPI(EGL_OPENGL_ES_API); if (UTILS_UNLIKELY(!bindAPI)) { std::cout << "eglBindAPI EGL_OPENGL_ES_API failed" << std::endl; return false; } _eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); assert_invariant(_eglDisplay != EGL_NO_DISPLAY); EGLint major, minor; EGLBoolean initialized = false; EGLDeviceEXT eglDevice; EGLint numDevices; if(auto* getPlatformDisplay = reinterpret_cast( eglGetProcAddress("eglGetPlatformDisplayEXT"))) { EGLint kD3D11DisplayAttributes[] = { EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE, EGL_TRUE, EGL_NONE, }; _eglDisplay = getPlatformDisplay(EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, kD3D11DisplayAttributes); initialized = eglInitialize(_eglDisplay, &major, &minor); } std::cout << "Got major " << major << " and minor " << minor << std::endl; if (UTILS_UNLIKELY(!initialized)) { std::cout << "eglInitialize failed" << std::endl; return false; } importGLESExtensionsEntryPoints(); EGLint configsCount; EGLint configAttribs[] = { EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_DEPTH_SIZE, 24, EGL_STENCIL_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_NONE }; EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE, EGL_NONE, // reserved for EGL_CONTEXT_OPENGL_NO_ERROR_KHR below EGL_NONE }; // find an opaque config if (!eglChooseConfig(_eglDisplay, configAttribs, &_eglConfig, 1, &configsCount)) { return false; } _context = (void*)eglCreateContext(_eglDisplay, _eglConfig, EGL_NO_CONTEXT, contextAttribs); if (UTILS_UNLIKELY(_context == EGL_NO_CONTEXT)) { return false; } } bool PolyvoxFilamentPlugin::MakeD3DTexture(uint32_t width, uint32_t height,std::unique_ptr> result) { importGLESExtensionsEntryPoints(); if(_active.get()) { result->Error("ERROR", "Texture already exists. You must call destroyTexture before attempting to create a new one."); return false; } _active = std::make_unique( _pluginRegistrar, _textureRegistrar, std::move(result), width, height, _D3D11Device, _D3D11DeviceContext, _eglConfig, _eglDisplay, _context ); return _active->flutterTextureId != -1; } #else bool PolyvoxFilamentPlugin::CreateSharedWGLContext() { HWND hwnd = _pluginRegistrar->GetView() ->GetNativeWindow(); HDC whdc = GetDC(hwnd); if (whdc == NULL) { std::cout << "No device context for temporary window" << std::endl; return false; } std::cout << "No GL context exists, creating" << std::endl; PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), 1, PFD_DRAW_TO_BITMAP | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, // Flags PFD_TYPE_RGBA, // The kind of framebuffer. RGBA or palette. 32, // Colordepth of the framebuffer. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, // Number of bits for the depthbuffer 0, // Number of bits for the stencilbuffer 0, // Number of Aux buffers in the framebuffer. PFD_MAIN_PLANE, 0, 0, 0, 0 }; int pixelFormat = ChoosePixelFormat(whdc, &pfd); SetPixelFormat(whdc, pixelFormat, &pfd); // We need a tmp context to retrieve and call wglCreateContextAttribsARB. HGLRC tempContext = wglCreateContext(whdc); if (!wglMakeCurrent(whdc, tempContext)) { std::cout <<"Failed to acquire temporary context" << std::endl; return false; } GLenum err = glGetError(); if(err != GL_NO_ERROR) { std::cout <<"GL Error @ 455 %d" << std::endl; return false; } PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribs = nullptr; wglCreateContextAttribs = (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress( "wglCreateContextAttribsARB"); if (!wglCreateContextAttribs) { std::cout <<"Failed to resolve wglCreateContextAttribsARB" << std::endl; return false; } for (int minor = 5; minor >= 1; minor--) { std::vector mAttribs = {WGL_CONTEXT_MAJOR_VERSION_ARB, 4, WGL_CONTEXT_MINOR_VERSION_ARB, minor, 0}; _context = wglCreateContextAttribs(whdc, nullptr, mAttribs.data()); if (_context) { break; } } wglMakeCurrent(NULL, NULL); wglDeleteContext(tempContext); if (!_context || !wglMakeCurrent(whdc, _context)) { std::cout << "Failed to create OpenGL context." << std::endl; return false; } } bool PolyvoxFilamentPlugin::MakeOpenGLTexture(uint32_t width, uint32_t height,std::unique_ptr> result) { if(_active.get()) { result->Error("ERROR", "Texture already exists. You must call destroyTexture before attempting to create a new one."); return false; } _active = std::make_unique( _pluginRegistrar, _textureRegistrar, std::move(result), width, height, _context, _renderMutex ); return _active->flutterTextureId != -1; } #endif void PolyvoxFilamentPlugin::CreateTexture( const flutter::MethodCall &methodCall, std::unique_ptr> result) { if(!_renderMutex.get()) { _renderMutex = std::make_shared(); } std::lock_guard guard(*(_renderMutex.get())); const auto *args = std::get_if(methodCall.arguments()); const auto width = (uint32_t)round(*(std::get_if(&(args->at(0))))); const auto height = (uint32_t)round(*(std::get_if(&(args->at(1))))); // create a single shared context for the life of the application // this will be used to create a backing texture and passed to Filament if(!_context) { #ifdef USE_ANGLE CreateSharedEGLContext(); #else CreateSharedWGLContext(); #endif } #ifdef USE_ANGLE bool success = MakeD3DTexture(width, height, std::move(result)); #else bool success = MakeOpenGLTexture(width, height, std::move(result)); #endif } void PolyvoxFilamentPlugin::DestroyTexture( const flutter::MethodCall &methodCall, std::unique_ptr> result) { const auto *flutterTextureId = std::get_if(methodCall.arguments()); if(!flutterTextureId) { result->Error("NOT_IMPLEMENTED", "Flutter texture ID must be provided"); return; } if(!_active) { result->Success("Texture has already been detroyed, ignoring"); return; } if(_active->flutterTextureId != *flutterTextureId) { result->Error("TEXTURE_MISMATCH", "Specified texture ID is not active"); return; } auto sh = std::make_shared>>(std::move(result)); _textureRegistrar->UnregisterTexture(_active->flutterTextureId, [=, sharedResult=std::move(sh) ]() { #ifdef USE_ANGLE this->_active = nullptr; #else if(this->_inactive) { HWND hwnd = _pluginRegistrar->GetView()->GetNativeWindow(); HDC whdc = GetDC(hwnd); if (!wglMakeCurrent(whdc, _context)) { std::cout << "Failed to switch OpenGL context in destructor." << std::endl; // result->Error("CONTEXT", "Failed to switch OpenGL context.", nullptr); return; } glDeleteTextures(1, &this->_inactive->glTextureId); wglMakeCurrent(NULL, NULL); } this->_inactive = std::move(this->_active); #endif auto unique = std::move(*(sharedResult.get())); unique->Success(flutter::EncodableValue(true)); std::cout << "Unregistered/destroyed texture." << std::endl; }); } void PolyvoxFilamentPlugin::HandleMethodCall( const flutter::MethodCall &methodCall, std::unique_ptr> result) { if (methodCall.method_name() == "getSharedContext") { result->Success(flutter::EncodableValue((int64_t)_context)); } else if (methodCall.method_name() == "getResourceLoaderWrapper") { const ResourceLoaderWrapper *const resourceLoader = new ResourceLoaderWrapper(_loadResource, _freeResource, this); result->Success(flutter::EncodableValue((int64_t)resourceLoader)); } else if (methodCall.method_name() == "createTexture") { CreateTexture(methodCall, std::move(result)); } else if (methodCall.method_name() == "destroyTexture") { DestroyTexture(methodCall, std::move(result)); } else if(methodCall.method_name() == "getRenderCallback") { flutter::EncodableList resultList; resultList.push_back(flutter::EncodableValue((int64_t)&render_callback)); resultList.push_back(flutter::EncodableValue((int64_t)this)); result->Success(resultList); } else if(methodCall.method_name() == "getDriverPlatform") { #ifdef USE_ANGLE result->Success(flutter::EncodableValue((int64_t)_platform)); #else result->Success(flutter::EncodableValue((int64_t)nullptr)); #endif } else { result->Error("NOT_IMPLEMENTED", "Method is not implemented %s", methodCall.method_name()); } } } // namespace polyvox_filament