Compare commits
4 Commits
52662e129b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97ff779911 | ||
|
|
dacbb2e043 | ||
|
|
27693a8fbf | ||
|
|
427c64ef5d |
44
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
44
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -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
|
||||
14
.github/ISSUE_TEMPLATE/config.yml
vendored
14
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -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.
|
||||
88
.github/workflows/ci.yml
vendored
88
.github/workflows/ci.yml
vendored
@@ -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 }}
|
||||
104
.github/workflows/make_build.yml
vendored
104
.github/workflows/make_build.yml
vendored
@@ -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
2
.gitignore
vendored
@@ -53,3 +53,5 @@ CMakeUserPresets.json
|
||||
|
||||
# stupid godot developers using SCons
|
||||
build/
|
||||
|
||||
godot/
|
||||
|
||||
@@ -49,14 +49,31 @@ project(gdvapi
|
||||
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)
|
||||
|
||||
target_link_libraries(${LIBNAME} PRIVATE
|
||||
Vulkan::Headers
|
||||
PkgConfig::AVFORMAT
|
||||
PkgConfig::AVCODEC
|
||||
PkgConfig::SWSCALE
|
||||
PkgConfig::AVUTIL
|
||||
)
|
||||
|
||||
target_sources(${LIBNAME}
|
||||
PRIVATE
|
||||
src/register_types.cpp
|
||||
src/register_types.h
|
||||
src/example_class.cpp
|
||||
src/example_class.h
|
||||
src/gdvapivideostream_class.cpp
|
||||
src/gdvapivideostream_class.h
|
||||
)
|
||||
|
||||
# Fetch a list of the xml files to use for documentation and add to our target
|
||||
|
||||
12
flake.nix
12
flake.nix
@@ -13,6 +13,18 @@
|
||||
clang
|
||||
python3
|
||||
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 = ''
|
||||
|
||||
BIN
project/badapple.mov
Normal file
BIN
project/badapple.mov
Normal file
Binary file not shown.
@@ -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"
|
||||
7
project/bin/gdvapi.gdextension
Normal file
7
project/bin/gdvapi.gdextension
Normal 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"
|
||||
1
project/bin/gdvapi.gdextension.uid
Normal file
1
project/bin/gdvapi.gdextension.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://vgmwthvwcljf
|
||||
@@ -2,5 +2,5 @@ extends Node
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var example := ExampleClass.new()
|
||||
example.print_type(example)
|
||||
#$GDVAPIVideoStream.begin("udp://0.0.0.0:5000")
|
||||
pass
|
||||
|
||||
1
project/example.gd.uid
Normal file
1
project/example.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bkwk40ybs8kgm
|
||||
@@ -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")
|
||||
|
||||
[node name="GDVAPIVideoStream" type="GDVAPIVideoStream" parent="." unique_id=1053894928]
|
||||
offset_right = 40.0
|
||||
offset_bottom = 40.0
|
||||
|
||||
49
project/export_presets.cfg
Normal file
49
project/export_presets.cfg
Normal 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}\""
|
||||
BIN
project/exports/badapple.mov
Normal file
BIN
project/exports/badapple.mov
Normal file
Binary file not shown.
BIN
project/exports/godot cpp template.pck
Normal file
BIN
project/exports/godot cpp template.pck
Normal file
Binary file not shown.
4
project/exports/godot cpp template.sh
Executable file
4
project/exports/godot cpp template.sh
Executable 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" "$@"
|
||||
BIN
project/exports/godot cpp template.x86_64
Executable file
BIN
project/exports/godot cpp template.x86_64
Executable file
Binary file not shown.
@@ -18,6 +18,8 @@ dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.cte
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
@@ -25,6 +27,10 @@ mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
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/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
|
||||
@@ -8,8 +8,18 @@
|
||||
|
||||
config_version=5
|
||||
|
||||
[animation]
|
||||
|
||||
compatibility/default_parent_skeleton_in_mesh_instance_3d=true
|
||||
|
||||
[application]
|
||||
|
||||
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"
|
||||
|
||||
[display]
|
||||
|
||||
window/size/viewport_width=1920
|
||||
window/size/viewport_height=1080
|
||||
|
||||
BIN
project/test.mp4
Normal file
BIN
project/test.mp4
Normal file
Binary file not shown.
2
server-ffmpeg.sh
Executable file
2
server-ffmpeg.sh
Executable 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
5
server-gst.sh
Normal 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
5
server_camera.sh
Executable 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
|
||||
@@ -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()));
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
324
src/gdvapivideostream_class.cpp
Normal file
324
src/gdvapivideostream_class.cpp
Normal 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();
|
||||
}
|
||||
67
src/gdvapivideostream_class.h
Normal file
67
src/gdvapivideostream_class.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -5,34 +5,32 @@
|
||||
#include <godot_cpp/core/defs.hpp>
|
||||
#include <godot_cpp/godot.hpp>
|
||||
|
||||
#include "example_class.h"
|
||||
#include "gdvapivideostream_class.h"
|
||||
|
||||
using namespace godot;
|
||||
|
||||
void initialize_gdextension_types(ModuleInitializationLevel p_level)
|
||||
{
|
||||
void initialize_gdextension_types(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
GDREGISTER_CLASS(ExampleClass);
|
||||
|
||||
GDREGISTER_RUNTIME_CLASS(GDVAPIVideoStream);
|
||||
}
|
||||
|
||||
|
||||
void uninitialize_gdextension_types(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C"
|
||||
{
|
||||
// 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);
|
||||
init_obj.register_initializer(initialize_gdextension_types);
|
||||
init_obj.register_terminator(uninitialize_gdextension_types);
|
||||
init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE);
|
||||
extern "C" {
|
||||
GDExtensionBool GDE_EXPORT gdvapi_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);
|
||||
init_obj.register_initializer(initialize_gdextension_types);
|
||||
init_obj.register_terminator(uninitialize_gdextension_types);
|
||||
init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE);
|
||||
|
||||
return init_obj.init();
|
||||
}
|
||||
return init_obj.init();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user