Compare commits

...

4 Commits

Author SHA1 Message Date
Rhys Tumelty
97ff779911 tried (and failed) to get a white rectangle placeholder drawing, also streaming btw 2026-08-24 16:18:39 +01:00
Rhys Tumelty
dacbb2e043 file decoding works 2026-08-23 21:50:09 +01:00
Rhys Tumelty
27693a8fbf made it acutally work in godot, and made it draw a texture 2026-08-23 20:59:48 +01:00
Rhys Tumelty
427c64ef5d whoops 2026-08-23 18:18:37 +01:00
30 changed files with 538 additions and 360 deletions

View File

@@ -1,44 +0,0 @@
name: Bug report
description: Report a bug with the godot-cpp template
body:
- type: markdown
attributes:
value: |
- Write a descriptive issue title above.
- Search [open](https://github.com/godotengine/godot-cpp-template/issues) and [closed](https://github.com/godotengine/godot-cpp-template/issues?q=is%3Aissue+is%3Aclosed) issues to ensure it has not already been reported.
- type: input
attributes:
label: Godot version
description: >
Specify the Git commit hash of your Godot build.
placeholder: v4.0.stable.official [92bee43ad]
validations:
required: true
- type: input
attributes:
label: godot-cpp version
description: >
Specify the Git commit hash of the godot-cpp submodule in your project. You can run `git status` inside the folder to check it.
placeholder: v4.0.stable.official [9d1c396c5]
validations:
required: true
- type: input
attributes:
label: System information
description: |
Specify the OS version.
placeholder: Windows 10
validations:
required: true
- type: textarea
attributes:
label: Issue description
description: |
Describe your issue briefly. What doesn't work, and how do you expect it to work instead?
You can include images or videos with drag and drop, and format code blocks or logs with <code>```</code> tags.
validations:
required: true

View File

@@ -1,14 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Godot proposals
url: https://github.com/godotengine/godot-proposals
about: Please submit feature proposals on the Godot proposals repository, not here.
- name: Godot documentation repository
url: https://github.com/godotengine/godot-docs
about: Please report issues with documentation on the Godot documentation repository, not here.
- name: Godot community channels
url: https://godotengine.org/community
about: Please ask for technical support on one of the other community channels, not here.

View File

@@ -1,88 +0,0 @@
# For syntax, see https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax
# This workflow is automatically triggered for every push and pull request, to verify that everything still builds.
name: Continuous Integration
on:
workflow_call:
push:
pull_request:
merge_group:
jobs:
build:
strategy:
fail-fast: false
matrix:
# Test a few selected combinations of parameters ("test case matrix").
# While it would be nice to test all needed combinations, in practice this would a waste of CI usage time.
# By building just with a few combinations, we only need a few runners,
# while still achieving adequate coverage.
# To make a full build with all combinations, use the make_build.yml workflow instead.
include:
- target: { platform: linux, arch: x86_64, os: ubuntu-22.04 }
target-type: template_debug
float-precision: double
- target: { platform: windows, arch: x86_64, os: windows-latest }
target-type: template_release
float-precision: single
- target: { platform: macos, arch: universal, os: macos-latest }
target-type: template_debug
float-precision: single
- target: { platform: android, arch: arm64, os: ubuntu-22.04 }
target-type: template_debug
float-precision: single
- target: { platform: web, arch: wasm32, os: ubuntu-22.04 }
target-type: template_release
float-precision: double
runs-on: ${{ matrix.target.os }}
steps:
# Clone this repository
- name: Checkout
uses: actions/checkout@v4
with:
submodules: true
# Lint
#- name: Setup clang-format
# shell: bash
# run: |
# python -m pip install clang-format
#- name: Run clang-format
# shell: bash
# run: |
# clang-format src/** --dry-run --Werror
# Add linux x86_32 toolchain
- name: Install multilib support
if: ${{ matrix.target.platform == 'linux' && matrix.target.arch == 'x86_32' }}
run: |
sudo apt-get update && sudo apt-get install -y gcc-multilib g++-multilib
# Setup dependencies
- name: Setup godot-cpp
uses: ./godot-cpp/.github/actions/setup-godot-cpp
with:
platform: ${{ matrix.target.platform }}
em-version: 3.1.62
# Build GDExtension (with caches)
- name: Restore .scons_cache
uses: ./godot-cpp/.github/actions/godot-cache-restore
with:
scons-cache: ${{ github.workspace }}/.scons-cache/
cache-name: ${{ matrix.target.platform }}_${{ matrix.target.arch }}_${{ matrix.float-precision }}_${{ matrix.target-type }}
- name: Build GDExtension Debug Build
shell: sh
env:
SCONS_CACHE: ${{ github.workspace }}/.scons-cache/
run: |
scons target=${{ matrix.target-type }} platform=${{ matrix.target.platform }} arch=${{ matrix.target.arch }} precision=${{ matrix.float-precision }}
- name: Save .scons_cache
uses: ./godot-cpp/.github/actions/godot-cache-save
with:
scons-cache: ${{ github.workspace }}/.scons-cache/
cache-name: ${{ matrix.target.platform }}_${{ matrix.target.arch }}_${{ matrix.float-precision }}_${{ matrix.target-type }}

View File

@@ -1,104 +0,0 @@
# For syntax, see https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax
# This workflow is triggered manually on the "Actions" tab on GitHub, and can be used to create releases.
name: Make a GDExtension build for all supported platforms
on:
workflow_dispatch:
jobs:
build:
strategy:
fail-fast: false
matrix:
# A build is made for every possible combination of parameters
# You can add or remove entries from the arrays of each parameter to customize which builds you want to run.
# See https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/running-variations-of-jobs-in-a-workflow
target:
[
{ platform: linux, arch: x86_64, os: ubuntu-22.04 },
{ platform: linux, arch: x86_32, os: ubuntu-22.04 },
{ platform: linux, arch: arm64, os: ubuntu-22.04-arm },
{ platform: linux, arch: arm32, os: ubuntu-22.04-arm },
{ platform: windows, arch: x86_64, os: windows-latest },
{ platform: windows, arch: x86_32, os: windows-latest },
{ platform: windows, arch: arm64, os: windows-latest },
{ platform: macos, arch: universal, os: macos-latest },
{ platform: android, arch: x86_64, os: ubuntu-22.04 },
{ platform: android, arch: x86_32, os: ubuntu-22.04 },
{ platform: android, arch: arm64, os: ubuntu-22.04 },
{ platform: android, arch: arm32, os: ubuntu-22.04 },
{ platform: ios, arch: arm64, os: macos-latest },
{ platform: web, arch: wasm32, os: ubuntu-22.04 },
]
target-type: [template_debug, template_release]
float-precision: [single]
runs-on: ${{ matrix.target.os }}
steps:
# Clone this repository
- name: Checkout
uses: actions/checkout@v4
with:
submodules: true
# Add linux x86_32 toolchain
- name: Install multilib support
if: ${{ matrix.target.platform == 'linux' && matrix.target.arch == 'x86_32' }}
run: |
sudo apt-get update && sudo apt-get install -y gcc-multilib g++-multilib
# Setup dependencies
- name: Setup godot-cpp
uses: ./godot-cpp/.github/actions/setup-godot-cpp
with:
platform: ${{ matrix.target.platform }}
em-version: 3.1.62
# Build GDExtension (with caches)
- name: Restore .scons_cache
uses: ./godot-cpp/.github/actions/godot-cache-restore
with:
scons-cache: ${{ github.workspace }}/.scons-cache/
cache-name: ${{ matrix.target.platform }}_${{ matrix.target.arch }}_${{ matrix.float-precision }}_${{ matrix.target-type }}
- name: Build GDExtension Debug Build
shell: sh
env:
SCONS_CACHE: ${{ github.workspace }}/.scons-cache/
run: |
scons target=${{ matrix.target-type }} platform=${{ matrix.target.platform }} arch=${{ matrix.target.arch }} precision=${{ matrix.float-precision }}
- name: Save .scons_cache
uses: ./godot-cpp/.github/actions/godot-cache-save
with:
scons-cache: ${{ github.workspace }}/.scons-cache/
cache-name: ${{ matrix.target.platform }}_${{ matrix.target.arch }}_${{ matrix.float-precision }}_${{ matrix.target-type }}
# Clean up compilation files
- name: Windows - Delete compilation files
if: ${{ matrix.target.platform == 'windows' }}
shell: pwsh
run: |
Remove-Item bin/* -Include *.exp,*.lib,*.pdb -Force
# Upload the build
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: godot-cpp-template-${{ matrix.target.platform }}-${{ matrix.target.arch }}-${{ matrix.float-precision }}-${{ matrix.target-type }}
path: |
${{ github.workspace }}/bin/**
# Merges all the build artifacts together into a single godot-cpp-template artifact.
# If you comment out this step, all the builds will be uploaded individually.
merge:
runs-on: ubuntu-22.04
needs: build
steps:
- name: Merge Artifacts
uses: actions/upload-artifact/merge@v4
with:
name: godot-cpp-template
pattern: godot-cpp-template-*
delete-merged: true

2
.gitignore vendored
View File

@@ -53,3 +53,5 @@ CMakeUserPresets.json
# stupid godot developers using SCons # stupid godot developers using SCons
build/ build/
godot/

View File

@@ -49,14 +49,31 @@ project(gdvapi
LANGUAGES CXX LANGUAGES CXX
) )
find_package(Vulkan REQUIRED)
find_package(PkgConfig REQUIRED)
pkg_check_modules(AVFORMAT REQUIRED IMPORTED_TARGET libavformat)
pkg_check_modules(AVCODEC REQUIRED IMPORTED_TARGET libavcodec)
pkg_check_modules(SWSCALE REQUIRED IMPORTED_TARGET libswscale)
pkg_check_modules(AVUTIL REQUIRED IMPORTED_TARGET libavutil)
add_library(${LIBNAME} SHARED) add_library(${LIBNAME} SHARED)
target_link_libraries(${LIBNAME} PRIVATE
Vulkan::Headers
PkgConfig::AVFORMAT
PkgConfig::AVCODEC
PkgConfig::SWSCALE
PkgConfig::AVUTIL
)
target_sources(${LIBNAME} target_sources(${LIBNAME}
PRIVATE PRIVATE
src/register_types.cpp src/register_types.cpp
src/register_types.h src/register_types.h
src/example_class.cpp src/gdvapivideostream_class.cpp
src/example_class.h src/gdvapivideostream_class.h
) )
# Fetch a list of the xml files to use for documentation and add to our target # Fetch a list of the xml files to use for documentation and add to our target

View File

@@ -13,6 +13,18 @@
clang clang
python3 python3
godot godot
pkg-config
gst_all_1.gstreamer
gst_all_1.gst-plugins-base
gst_all_1.gst-plugins-good
gst_all_1.gst-plugins-bad
];
buildInputs = with pkgs; [
vulkan-loader
vulkan-headers
ffmpeg
]; ];
shellHook = '' shellHook = ''

BIN
project/badapple.mov Normal file

Binary file not shown.

View File

@@ -1,57 +0,0 @@
[configuration]
entry_symbol = "example_library_init"
compatibility_minimum = "4.1"
reloadable = false
[libraries]
; Relative paths ensure that our GDExtension can be placed anywhere in the project directory.
macos.single.debug = "./macos/libEXTENSION-NAME.macos.template_debug.dylib"
macos.double.debug = "./macos/libEXTENSION-NAME.macos.template_debug.double.dylib"
macos.single.release = "./macos/libEXTENSION-NAME.macos.template_release.dylib"
macos.double.release = "./macos/libEXTENSION-NAME.macos.template_release.double.dylib"
ios.arm64.single.debug = "./ios/libEXTENSION-NAME.ios.template_debug.arm64.dylib"
ios.arm64.double.debug = "./ios/libEXTENSION-NAME.ios.template_debug.arm64.double.dylib"
ios.arm64.single.release = "./ios/libEXTENSION-NAME.ios.template_release.arm64.dylib"
ios.arm64.double.release = "./ios/libEXTENSION-NAME.ios.template_release.arm64.double.dylib"
windows.x86_32.single.debug = "./windows/EXTENSION-NAME.windows.template_debug.x86_32.dll"
windows.x86_32.double.debug = "./windows/EXTENSION-NAME.windows.template_debug.x86_32.double.dll"
windows.x86_32.single.release = "./windows/EXTENSION-NAME.windows.template_release.x86_32.dll"
windows.x86_32.double.release = "./windows/EXTENSION-NAME.windows.template_release.x86_32.double.dll"
windows.x86_64.single.debug = "./windows/EXTENSION-NAME.windows.template_debug.x86_64.dll"
windows.x86_64.double.debug = "./windows/EXTENSION-NAME.windows.template_debug.x86_64.double.dll"
windows.x86_64.single.release = "./windows/EXTENSION-NAME.windows.template_release.x86_64.dll"
windows.x86_64.double.release = "./windows/EXTENSION-NAME.windows.template_release.x86_64.double.dll"
linux.x86_64.single.debug = "./linux/libEXTENSION-NAME.linux.template_debug.x86_64.so"
linux.x86_64.double.debug = "./linux/libEXTENSION-NAME.linux.template_debug.x86_64.double.so"
linux.x86_64.single.release = "./linux/libEXTENSION-NAME.linux.template_release.x86_64.so"
linux.x86_64.double.release = "./linux/libEXTENSION-NAME.linux.template_release.x86_64.double.so"
linux.arm64.single.debug = "./linux/libEXTENSION-NAME.linux.template_debug.arm64.so"
linux.arm64.double.debug = "./linux/libEXTENSION-NAME.linux.template_debug.arm64.double.so"
linux.arm64.single.release = "./linux/libEXTENSION-NAME.linux.template_release.arm64.so"
linux.arm64.double.release = "./linux/libEXTENSION-NAME.linux.template_release.arm64.double.so"
linux.rv64.single.debug = "./linux/libEXTENSION-NAME.linux.template_debug.rv64.so"
linux.rv64.double.debug = "./linux/libEXTENSION-NAME.linux.template_debug.rv64.double.so"
linux.rv64.single.release = "./linux/libEXTENSION-NAME.linux.template_release.rv64.so"
linux.rv64.double.release = "./linux/libEXTENSION-NAME.linux.template_release.rv64.double.so"
android.x86_64.single.debug = "./android/libEXTENSION-NAME.android.template_debug.x86_64.so"
android.x86_64.double.debug = "./android/libEXTENSION-NAME.android.template_debug.x86_64.double.so"
android.x86_64.single.release = "./android/libEXTENSION-NAME.android.template_release.x86_64.so"
android.x86_64.double.release = "./android/libEXTENSION-NAME.android.template_release.x86_64.double.so"
android.arm64.single.debug = "./android/libEXTENSION-NAME.android.template_debug.arm64.so"
android.arm64.double.debug = "./android/libEXTENSION-NAME.android.template_debug.arm64.double.so"
android.arm64.single.release = "./android/libEXTENSION-NAME.android.template_release.arm64.so"
android.arm64.double.release = "./android/libEXTENSION-NAME.android.template_release.arm64.double.so"
web.wasm32.single.debug = "./web/libEXTENSION-NAME.web.template_debug.wasm32.nothreads.wasm"
web.wasm32.double.debug = "./web/libEXTENSION-NAME.web.template_debug.wasm32.double.nothreads.wasm"
web.wasm32.single.release = "./web/libEXTENSION-NAME.web.template_release.wasm32.nothreads.wasm"
web.wasm32.double.release = "./web/libEXTENSION-NAME.web.template_release.wasm32.double.nothreads.wasm"

View File

@@ -0,0 +1,7 @@
[configuration]
entry_symbol = "gdvapi_library_init"
compatibility_minimum = "4.7"
[libraries]
linux.editor.x86_64 = "res://bin/linux/gdvapi.linux.editor.x86_64.so"
linux.template_debug.x86_64 = "res://bin/linux/gdvapi.linux.template_debug.x86_64.so"

View File

@@ -0,0 +1 @@
uid://vgmwthvwcljf

View File

@@ -2,5 +2,5 @@ extends Node
func _ready() -> void: func _ready() -> void:
var example := ExampleClass.new() #$GDVAPIVideoStream.begin("udp://0.0.0.0:5000")
example.print_type(example) pass

1
project/example.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://bkwk40ybs8kgm

View File

@@ -1,6 +1,10 @@
[gd_scene load_steps=2 format=3 uid="uid://dh0oj81pufivs"] [gd_scene format=3 uid="uid://dh0oj81pufivs"]
[ext_resource type="Script" path="res://example.gd" id="1_jdh55"] [ext_resource type="Script" uid="uid://bkwk40ybs8kgm" path="res://example.gd" id="1_jdh55"]
[node name="Node" type="Node"] [node name="Node" type="Node" unique_id=2080300232]
script = ExtResource("1_jdh55") script = ExtResource("1_jdh55")
[node name="GDVAPIVideoStream" type="GDVAPIVideoStream" parent="." unique_id=1053894928]
offset_right = 40.0
offset_bottom = 40.0

View File

@@ -0,0 +1,49 @@
[runnable_presets]
Linux="Linux"
[preset.0]
name="Linux"
platform="Linux"
dedicated_server=false
custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter=""
export_path="exports/godot cpp template.x86_64"
patches=PackedStringArray()
patch_delta_encoding=false
patch_delta_compression_level_zstd=19
patch_delta_min_reduction=0.1
patch_delta_include_filters="*"
patch_delta_exclude_filters=""
encryption_include_filters=""
encryption_exclude_filters=""
seed=0
encrypt_pck=false
encrypt_directory=false
script_export_mode=2
[preset.0.options]
custom_template/debug=""
custom_template/release=""
debug/export_console_wrapper=1
binary_format/embed_pck=false
texture_format/s3tc_bptc=true
texture_format/etc2_astc=false
shader_baker/enabled=false
binary_format/architecture="x86_64"
ssh_remote_deploy/enabled=false
ssh_remote_deploy/host="user@host_ip"
ssh_remote_deploy/port="22"
ssh_remote_deploy/extra_args_ssh=""
ssh_remote_deploy/extra_args_scp=""
ssh_remote_deploy/run_script="#!/usr/bin/env bash
export DISPLAY=:0
unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\"
\"{temp_dir}/{exe_name}\" {cmd_args}"
ssh_remote_deploy/cleanup_script="#!/usr/bin/env bash
pkill -x -f \"{temp_dir}/{exe_name} {cmd_args}\"
rm -rf \"{temp_dir}\""

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,4 @@
#!/bin/sh
printf '\033c\033]0;%s\a' godot cpp template
base_path="$(dirname "$(realpath "$0")")"
"$base_path/godot cpp template.x86_64" "$@"

Binary file not shown.

View File

@@ -18,6 +18,8 @@ dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.cte
compress/mode=0 compress/mode=0
compress/high_quality=false compress/high_quality=false
compress/lossy_quality=0.7 compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1 compress/hdr_compression=1
compress/normal_map=0 compress/normal_map=0
compress/channel_pack=0 compress/channel_pack=0
@@ -25,6 +27,10 @@ mipmaps/generate=false
mipmaps/limit=-1 mipmaps/limit=-1
roughness/mode=0 roughness/mode=0
roughness/src_normal="" roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true process/fix_alpha_border=true
process/premult_alpha=false process/premult_alpha=false
process/normal_map_invert_y=false process/normal_map_invert_y=false

View File

@@ -8,8 +8,18 @@
config_version=5 config_version=5
[animation]
compatibility/default_parent_skeleton_in_mesh_instance_3d=true
[application] [application]
config/name="godot cpp template" config/name="godot cpp template"
config/features=PackedStringArray("4.1", "Forward Plus") run/main_scene="uid://dh0oj81pufivs"
config/features=PackedStringArray("4.7", "Forward Plus")
config/icon="res://icon.svg" config/icon="res://icon.svg"
[display]
window/size/viewport_width=1920
window/size/viewport_height=1080

BIN
project/test.mp4 Normal file

Binary file not shown.

2
server-ffmpeg.sh Executable file
View File

@@ -0,0 +1,2 @@
#!/bin/sh
ffmpeg -re -i $1 -c copy -f mpegts udp://127.0.0.1:5000

5
server-gst.sh Normal file
View File

@@ -0,0 +1,5 @@
#!/bin/sh
gst-launch-1.0 filesrc location=$1 ! qtdemux name=demux \
demux.video_0 ! queue ! h264parse ! mpegtsmux name=mux ! udpsink host=127.0.0.1 port=5000 \
demux.audio_0 ! queue ! aacparse ! mux.

5
server_camera.sh Executable file
View File

@@ -0,0 +1,5 @@
#!/bin/sh
ffmpeg -f v4l2 -input_format mjpeg -video_size 1920x1080 -framerate 30 -i "$1" \
-c:v libx264 -preset ultrafast -tune zerolatency -g 30 -flags +global_header \
-f mpegts udp://127.0.0.1:5000?pkt_size=1316

View File

@@ -1,9 +0,0 @@
#include "example_class.h"
void ExampleClass::_bind_methods() {
godot::ClassDB::bind_method(D_METHOD("print_type", "variant"), &ExampleClass::print_type);
}
void ExampleClass::print_type(const Variant &p_variant) const {
print_line(vformat("Type: %d", p_variant.get_type()));
}

View File

@@ -1,20 +0,0 @@
#pragma once
#include "godot_cpp/classes/ref_counted.hpp"
#include "godot_cpp/classes/wrapped.hpp"
#include "godot_cpp/variant/variant.hpp"
using namespace godot;
class ExampleClass : public RefCounted {
GDCLASS(ExampleClass, RefCounted)
protected:
static void _bind_methods();
public:
ExampleClass() = default;
~ExampleClass() override = default;
void print_type(const Variant &p_variant) const;
};

View File

@@ -0,0 +1,324 @@
#include "gdvapivideostream_class.h"
#include "godot_cpp/classes/project_settings.hpp"
#include "godot_cpp/classes/rd_texture_format.hpp"
#include "godot_cpp/classes/rd_texture_view.hpp"
#include "godot_cpp/classes/rendering_device.hpp"
#include "godot_cpp/classes/rendering_server.hpp"
#include "godot_cpp/variant/utility_functions.hpp"
#include "godot_cpp/variant/color.hpp"
#include "godot_cpp/variant/rect2.hpp"
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/hwcontext.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
}
using namespace godot;
enum HWMode { HW_NONE,
HW_VULKAN,
HW_VAAPI };
static HWMode active_hw_mode = HW_NONE;
void GDVAPIVideoStream::_bind_methods() {
godot::ClassDB::bind_method(D_METHOD("begin", "source"), &GDVAPIVideoStream::begin);
ClassDB::bind_method(D_METHOD("get_desired_width"), &GDVAPIVideoStream::get_desired_width);
ClassDB::bind_method(D_METHOD("set_desired_width", "v"), &GDVAPIVideoStream::set_desired_width);
ClassDB::bind_method(D_METHOD("get_desired_height"), &GDVAPIVideoStream::get_desired_height);
ClassDB::bind_method(D_METHOD("set_desired_height", "v"), &GDVAPIVideoStream::set_desired_height);
ADD_PROPERTY(PropertyInfo(Variant::INT, "desired_width"), "set_desired_width", "get_desired_width");
ADD_PROPERTY(PropertyInfo(Variant::INT, "desired_height"), "set_desired_height", "get_desired_height");
}
static enum AVPixelFormat get_hw_format(AVCodecContext *ctx, const enum AVPixelFormat *formats) {
for (const enum AVPixelFormat *p = formats; *p != -1; p++) {
if (active_hw_mode == HW_VULKAN && *p == AV_PIX_FMT_VULKAN) {
return AV_PIX_FMT_VULKAN;
}
if (active_hw_mode == HW_VAAPI && *p == AV_PIX_FMT_VAAPI) {
return AV_PIX_FMT_VAAPI;
}
}
UtilityFunctions::printerr("[gdvapi] hardware decoding unavailable");
active_hw_mode = HW_NONE;
return AV_PIX_FMT_NONE;
}
void GDVAPIVideoStream::begin(const Variant &source) {
String res_path = source;
String global_path = ProjectSettings::get_singleton()->globalize_path(res_path);
CharString path_utf8 = global_path.utf8();
fmt_ctx = nullptr;
if (avformat_open_input(&fmt_ctx, path_utf8.get_data(), nullptr, nullptr) < 0) {
UtilityFunctions::printerr("[gdvapi] failed to open input stream");
return;
}
if (avformat_find_stream_info(fmt_ctx, nullptr) < 0) {
return;
}
video_stream_idx = -1;
for (unsigned int i = 0; i < fmt_ctx->nb_streams; i++) {
if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
video_stream_idx = i;
break;
}
}
if (video_stream_idx == -1) return;
AVStream *stream = fmt_ctx->streams[video_stream_idx];
if (stream->avg_frame_rate.den > 0 && stream->avg_frame_rate.num > 0) {
double fps = av_q2d(stream->avg_frame_rate);
frame_delay = 1.0 / fps;
UtilityFunctions::print("[gdvapi] fps: ", fps, " frame delay: ", frame_delay, "s");
} else {
frame_delay = 1.0 / 60.0; // fallback
}
AVCodecParameters *codec_par = fmt_ctx->streams[video_stream_idx]->codecpar;
codec = avcodec_find_decoder(codec_par->codec_id);
if (!codec)
return;
codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx)
return;
if (avcodec_parameters_to_context(codec_ctx, codec_par) < 0)
return;
active_hw_mode = HW_VULKAN;
if (av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_VULKAN, nullptr, nullptr, 0) < 0) {
UtilityFunctions::print("[gdvapi] vulkan video extensions missing on driver, trying va-api");
active_hw_mode = HW_VAAPI;
if (av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_VAAPI, nullptr, nullptr, 0) < 0) {
UtilityFunctions::print("[gdvapi] va-api acceleration unavailable. using software");
active_hw_mode = HW_NONE;
}
}
if (active_hw_mode != HW_NONE) {
codec_ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx);
codec_ctx->get_format = get_hw_format;
}
if (avcodec_open2(codec_ctx, codec, nullptr) < 0)
return;
frame = av_frame_alloc();
sw_frame = av_frame_alloc();
packet = av_packet_alloc();
width = codec_ctx->width;
height = codec_ctx->height;
texture_init();
sws_ctx = sws_getContext(
width, height, codec_ctx->pix_fmt,
width, height, AV_PIX_FMT_RGBA,
SWS_BILINEAR, nullptr, nullptr, nullptr);
time_accumulator = 0.0;
set_process(true);
}
void GDVAPIVideoStream::_process(double delta) {
if (!fmt_ctx || !codec_ctx) return;
time_accumulator += delta;
if (time_accumulator < frame_delay) {
return;
}
time_accumulator -= frame_delay;
bool frame_finished = false;
while (!frame_finished && av_read_frame(fmt_ctx, packet) >= 0) {
if (packet->stream_index == video_stream_idx) {
if (avcodec_send_packet(codec_ctx, packet) >= 0) {
while (avcodec_receive_frame(codec_ctx, frame) >= 0) {
frame_finished = true;
RenderingDevice *rd = RenderingServer::get_singleton()->get_rendering_device();
if (!rd || !texture_rd_rid.is_valid())
continue;
if (frame->format == AV_PIX_FMT_VULKAN || frame->format == AV_PIX_FMT_VAAPI) {
if (av_hwframe_transfer_data(sw_frame, frame, 0) >= 0) {
PackedByteArray gpu_bytes;
gpu_bytes.resize(width * height * 4);
uint8_t *dest_pointers[4] = { gpu_bytes.ptrw(), nullptr, nullptr, nullptr };
int dest_linesizes[4] = { width * 4, 0, 0, 0 };
enum AVPixelFormat src_fmt = (enum AVPixelFormat)sw_frame->format;
struct SwsContext *hw_sws = sws_getContext(
width, height, src_fmt,
width, height, AV_PIX_FMT_RGBA,
SWS_BILINEAR, nullptr, nullptr, nullptr);
if (hw_sws) {
sws_scale(hw_sws, sw_frame->data, sw_frame->linesize, 0, height, dest_pointers, dest_linesizes);
sws_freeContext(hw_sws);
rd->texture_update(texture_rd_rid, 0, gpu_bytes);
}
}
} else if (frame->data[0] != nullptr) {
PackedByteArray rgba_data;
rgba_data.resize(width * height * 4);
uint8_t *dest_pointers[4] = { rgba_data.ptrw(), nullptr, nullptr, nullptr };
int dest_linesizes[4] = { width * 4, 0, 0, 0 };
if (sws_ctx) {
sws_scale(sws_ctx, frame->data, frame->linesize, 0, height, dest_pointers, dest_linesizes);
rd->texture_update(texture_rd_rid, 0, rgba_data);
}
}
queue_redraw();
}
}
}
av_packet_unref(packet);
}
}
GDVAPIVideoStream::~GDVAPIVideoStream() {
cleanup();
}
void GDVAPIVideoStream::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_EXIT_TREE: {
cleanup();
} break;
}
}
void GDVAPIVideoStream::texture_init() {
RenderingDevice *rd = RenderingServer::get_singleton()->get_rendering_device();
if (!rd)
return;
if (texture_rd_rid.is_valid()) {
rd->free_rid(texture_rd_rid);
}
Ref<RDTextureFormat> format;
format.instantiate();
format->set_texture_type(RenderingDevice::TEXTURE_TYPE_2D);
format->set_format(RenderingDevice::DATA_FORMAT_R8G8B8A8_UNORM);
format->set_width(width);
format->set_height(height);
format->set_depth(1);
format->set_array_layers(1);
format->set_mipmaps(1);
format->set_usage_bits(RenderingDevice::TEXTURE_USAGE_SAMPLING_BIT |
RenderingDevice::TEXTURE_USAGE_CAN_UPDATE_BIT);
PackedByteArray byte_array;
byte_array.resize(width * height * 4);
TypedArray<PackedByteArray> data;
data.push_back(byte_array);
Ref<RDTextureView> view;
view.instantiate();
texture_rd_rid = rd->texture_create(format, view, data);
if (texture_rd_rid.is_valid()) {
godot_texture.instantiate();
godot_texture->set_texture_rd_rid(texture_rd_rid);
}
}
void GDVAPIVideoStream::cleanup() {
set_process(false);
RenderingDevice *rd = RenderingServer::get_singleton()->get_rendering_device();
godot_texture.unref();
if (rd && texture_rd_rid.is_valid()) {
rd->free_rid(texture_rd_rid);
texture_rd_rid = RID();
}
if (sws_ctx) {
sws_freeContext(sws_ctx);
sws_ctx = nullptr;
}
if (frame) {
av_frame_free(&frame);
}
if (sw_frame) {
av_frame_free(&sw_frame);
}
if (packet) {
av_packet_free(&packet);
}
if (codec_ctx) {
avcodec_free_context(&codec_ctx);
}
if (hw_device_ctx) {
av_buffer_unref(&hw_device_ctx);
hw_device_ctx = nullptr;
}
if (fmt_ctx) {
avformat_close_input(&fmt_ctx);
}
}
void GDVAPIVideoStream::_draw() {
UtilityFunctions::print("_draw called");
if (godot_texture.is_valid()) {
draw_texture(godot_texture, Point2(0, 0));
} else {
Rect2 rect = Rect2(0, 0, desiredWidth, desiredHeight);
Color color = Color(1.0, 1.0, 1.0, 1.0);
draw_rect(rect, color);
}
}
void GDVAPIVideoStream::set_desired_width(const int v) {
if (desiredWidth != v) {
desiredWidth = v;
queue_redraw();
}
}
void GDVAPIVideoStream::set_desired_height(const int v) {
if (desiredHeight != v) {
desiredHeight = v;
queue_redraw();
}
}
int GDVAPIVideoStream::get_desired_width() const {
return desiredWidth;
}
int GDVAPIVideoStream::get_desired_height() const {
return desiredHeight;
}
void GDVAPIVideoStream::_ready() {
UtilityFunctions::print("_ready called");
queue_redraw();
}

View File

@@ -0,0 +1,67 @@
#pragma once
#include "godot_cpp/classes/control.hpp"
#include "godot_cpp/classes/texture2drd.hpp"
#include "godot_cpp/variant/rid.hpp"
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
}
namespace godot {
class GDVAPIVideoStream : public Control {
GDCLASS(GDVAPIVideoStream, Control)
private:
AVFormatContext *fmt_ctx = nullptr;
AVCodecContext *codec_ctx = nullptr;
const AVCodec *codec = nullptr;
AVFrame *frame = nullptr;
AVPacket *packet = nullptr;
struct SwsContext *sws_ctx = nullptr;
int video_stream_idx = -1;
int width = 256;
int height = 256;
AVBufferRef *hw_device_ctx = nullptr;
AVFrame *sw_frame = nullptr;
double frame_delay = 0.0;
double time_accumulator = 0.0;
RID texture_rd_rid;
Ref<Texture2DRD> godot_texture;
int desiredWidth = 256;
int desiredHeight = 256;
void cleanup();
void texture_init();
protected:
static void _bind_methods();
public:
GDVAPIVideoStream() = default;
~GDVAPIVideoStream() override;
void begin(const Variant &source);
void _process(double delta) override;
void _notification(int p_what);
void _draw() override;
void _ready() override;
void set_desired_width(const int v);
int get_desired_width() const;
void set_desired_height(const int v);
int get_desired_height() const;
};
}

View File

@@ -5,34 +5,32 @@
#include <godot_cpp/core/defs.hpp> #include <godot_cpp/core/defs.hpp>
#include <godot_cpp/godot.hpp> #include <godot_cpp/godot.hpp>
#include "example_class.h" #include "gdvapivideostream_class.h"
using namespace godot; using namespace godot;
void initialize_gdextension_types(ModuleInitializationLevel p_level) void initialize_gdextension_types(ModuleInitializationLevel p_level) {
{
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return; return;
} }
GDREGISTER_CLASS(ExampleClass);
GDREGISTER_RUNTIME_CLASS(GDVAPIVideoStream);
} }
void uninitialize_gdextension_types(ModuleInitializationLevel p_level) { void uninitialize_gdextension_types(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return; return;
} }
} }
extern "C" extern "C" {
{ GDExtensionBool GDE_EXPORT gdvapi_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) {
// Initialization
GDExtensionBool GDE_EXPORT example_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization)
{
GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization); GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization);
init_obj.register_initializer(initialize_gdextension_types); init_obj.register_initializer(initialize_gdextension_types);
init_obj.register_terminator(uninitialize_gdextension_types); init_obj.register_terminator(uninitialize_gdextension_types);
init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE); init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE);
return init_obj.init(); return init_obj.init();
} }
} }