adopt rawdrawandroidRepo to make native app

This commit is contained in:
Alexander Mahr 2024-12-31 18:09:46 +01:00
parent f83bf386cd
commit fb057ac80d
76 changed files with 25162 additions and 146 deletions

1
.gitignore vendored
View file

@ -4,3 +4,4 @@
!/app/obj/.gitkeep
/app/app.apk
/app/Makefile.app-config
/app/output.map

View file

@ -1,7 +1,10 @@
# Android App (build via docker/podman)
# Android App Written in C (build via docker/podman)
The goal of this repo, is to create a container that can serve to produce an "empty android Application" (i.e `app.apk` file).
As such the philosophy is to keep the process "simple" as to now make the understanding too difficult.
This repo provides its user with the ability to setup and compile a Android-App that is
based on `android.app.NativeActiviy` and thus can be completely compiled C source-code.
It is an adoptation (and hopefully somehow simplification of this repo [`rawdrawandroid`](https://github.com/cnlohr/rawdrawandroid)
and this many thanks and credits to most of the c-code goes to CNLohr.
## usage
@ -12,7 +15,7 @@ As such the philosophy is to keep the process "simple" as to now make the unders
./build-android-app.sh
```
2. follow the configuration
2. follow the configuration (configuration is less than in the normal case of [android-app](https://git.alexmahr.de/lion/android-app) as the API-Level is inserted fixed as "29" )
3. upon success the apk file created is in app/result/app.apk and can be installed via `adb`
```
adb install -r app/result/app.apk
@ -22,6 +25,7 @@ adb install -r app/result/app.apk
* work within container (debian based image)
* use Makefile as a build tool
* avoid the "unhappiness" of having to deal with neither KOTLIN nor JAVA
## benefits of this (compared to AndroidStudio)
@ -31,63 +35,4 @@ adb install -r app/result/app.apk
* oftentimes faster compile time (as compared with AndroidStudio Gradle builds)
* quick "webview" which can serve as starting point for people that can to PWA and websites
* Assisted initial configuration provides access to configure almost all types of [Android app permissions](app/.Makefile.scripts/make--app-config.sh#L127)
* no need to have Kotlin, Gradle setup
/
## basic info
This repo should allow to generate an empty "android app". by simply cloning this repo and
```
./build-android-app.sh
```
It does so via:
1. building a container (in any of the runtime/daemons it finds: i.e. docker,podman,etc..)
2. running this container having the `./app` folder being mounted within as `/app`
3. executing the [`app/Makefile`](app/Makefile) which will then:
4. either work with the configuration stored in an `app/app-config.sh` in case such file exists or
5. if not go through a `whiptail` text menu wizzard to configure a new empty app. (Makefile recipe: `./app-config.sh`)
6. it will then download the required android sdk files as necessary (Makefile recipe: `./android-sdk/installed`)
7. go through the further steps to setup the blank app.
## files and purpose
Upon `clone` of this repo the `app` folder is setup with these files:
```
# The (GNU) Makefile which...
app/Makefile
# ... has recipes that call scripts in folder....
app/.Makefile.scripts
# .. which creates an `app-config.sh`, a file to keep
# the configuration (app name,lable,api-levels,permissions etc)...
app/.Makefile.scripts/make--app-config.sh
# .. which creates an `AndroidManifest.xml`
app/.Makefile.scripts/make--AndroidManifest.xml
# .. which creates an `AppActivity.java` file (which just setup a Webview and loads `assets/index.html`)
app/.Makefile.scripts/make--AppActivity.java.sh
# .. which installs the necessary Android SDK in the correct versions
app/.Makefile.scripts/make--android-sdk.sh
app/assets
# the index.html file
app/assets/index.html
app/res
app/res/drawable
# the icon of the app
app/res/drawable/appicon.xml
```
Upon further `./build-android-app.sh` execution more folders will appear
```
# a folder in which the Android-sdk stuff (installed via sdkmanager) is stored
android-sdk
# folders used during build...
# ... for temporary files
bin/
obj/
result/
# app configuration resulting from text whiptail menu
app-config.sh
# the Manifest file as resulted from data from app-config.sh
AndroidManifest.xml
```
* no need to have Kotlin, Gradle setup, nor use JAVA

View file

@ -13,11 +13,12 @@ cat > "AndroidManifest.xml" << ANDROIDMANIFEST
<uses-sdk android:minSdkVersion="$APP_VERSION_SDK_MIN"
android:targetSdkVersion="$APP_VERSION_SDK_TARGET"/>
$(sed 's/^/ /' <<< "$APP_PERMISSIONS")
<application android:label="$APP_NAME" android:icon="@drawable/appicon">
<activity android:name="$APP_PACKAGE.AppActivity"
<application android:debuggable="true" android:hasCode="false" android:label="$APP_NAME" android:icon="@drawable/appicon">
<activity android:name="android.app.NativeActivity"
android:exported="true"
android:configChanges="orientation|screenSize|keyboardHidden"
android:label="$APP_NAME">
<meta-data android:name="android.app.lib_name" android:value="nativecode"/>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@ -26,4 +27,3 @@ $(sed 's/^/ /' <<< "$APP_PERMISSIONS")
</application>
</manifest>
ANDROIDMANIFEST

View file

@ -34,46 +34,7 @@ type sdkmanager 2>/dev/null || (
mv -v cmdline-tools "$BUILD_TOOLS_LATEST" || true
)
test -f android-sdk/.installed.buildtools.version.$APP_VERSION_SDK_TARGET || {
EXACTVERSION_BUILDTOOLS="$(
sdkmanager --list 2>/dev/null |
sed 's/^ *//' |
grep -e 'build-tools;'"$APP_VERSION_SDK_TARGET"'\.[0-9]*\.[0-9]*\ ' |
cut -f1 -d' ' |
sort |
tail -n 1
)"
test -z "$EXACTVERSION_BUILDTOOLS" && {
echo "ERROR cannot get '$EXACTVERSION_BUILDTOOLS'" >&2
echo "dropping to shell..." >&2
exec bash
}
sdkmanager --list_installed | grep -q "$EXACTVERSION_BUILDTOOLS" || {
sdkmanager --install "$EXACTVERSION_BUILDTOOLS"
}
echo "$EXACTVERSION_BUILDTOOLS" > android-sdk/.installed.buildtools.version.$APP_VERSION_SDK_TARGET
ln -rvsf android-sdk/.installed.buildtools.version.{$APP_VERSION_SDK_TARGET,current}
test -f android-sdk/installed || {
sdkmanager --install "build-tools;29.0.3" "cmake;3.10.2.4988404" "ndk;21.1.6352462" "platform-tools" "platforms;android-29" "tools" &&
touch android-sdk/installed || exit 1
}
test -f android-sdk/.installed.platforms.version.$APP_VERSION_SDK_TARGET || {
EXACTVERSION_PLATFORM="$(
sdkmanager --list 2>/dev/null |
sed 's/^ *//' |
grep -e 'platforms;android-'"$APP_VERSION_SDK_TARGET"'\ ' |
cut -f1 -d' ' |
sort |
tail -n 1
)"
test -z "$EXACTVERSION_PLATFORM" && {
echo "ERROR cannot get '$EXACTVERSION_PLATFORM'" >&2
echo "dropping to shell..." >&2
exec bash
}
sdkmanager --list_installed | grep -q "$EXACTVERSION_PLATFORM" || {
sdkmanager --install "$EXACTVERSION_PLATFORM"
}
echo "$EXACTVERSION_PLATFORM" > android-sdk/.installed.platforms.version.$APP_VERSION_SDK_TARGET
ln -rvsf android-sdk/.installed.platforms.version.{$APP_VERSION_SDK_TARGET,current}
}
touch android-sdk/installed

View file

@ -3,7 +3,8 @@
test -f app-config.sh && {
source app-config.sh
}
APP_VERSION_SDK_TARGET='29'
APP_VERSION_SDK_MIN='29'
set -e

View file

@ -9,48 +9,88 @@ SHELL=/bin/makefile-bash-wrapper.sh
# (as explained https://make.mad-scientist.net/papers/advanced-auto-dependency-generation/#include)
include Makefile.app-config
# litanay of variabes used
NDK:=./android-sdk/ndk/21.1.6352462
NDK_TOOLCHAIN:=$(NDK)/toolchains/llvm/prebuilt/linux-x86_64
NDK_TOOLCHAIN_INCLUDE:=$(NDK_TOOLCHAIN)/sysroot/usr/include
ANDROIDVERSION:=29
CC_ARM64:=$(NDK_TOOLCHAIN)/bin/aarch64-linux-android29-clang
CFLAGS?=-ffunction-sections -Os -fdata-sections -Wall -fvisibility=hidden
CFLAGS += -fvisibility=hidden
# APP_PACKAGE is set to (-D) i.e preprocessor value and used to set string when logging
CFLAGS+=-Os -DANDROID -DAPP_PACKAGE=\"$(APP_PACKAGE)\"
# the -D will likely set a Variable to be used in the Preprocessor
CFLAGS +=-DANDROID_FULLSCREEN
CFLAGS+= -I./src/nativecode/rawdraw -I./src/nativecode -I$(NDK)/sysroot/usr/include
CFLAGS+= -I$(NDK)/sysroot/usr/include/android -I$(NDK_TOOLCHAIN_INCLUDE) -I$(NDK_TOOLCHAIN_INCLUDE)/android
CFLAGS+= -fPIC -DANDROIDVERSION=$(ANDROIDVERSION)
CFLAGS_ARM64:=-m64
LDFLAGS?=-Wl,--gc-sections -Wl,-Map=output.map -s
LDFLAGS += -s
# LDFLAGS += -static-libstdc++
LDFLAGS += -lm -lGLESv3 -lEGL -landroid -llog -lOpenSLES
LDFLAGS += -shared -uANativeActivity_onCreate
# locatoins of "mostly c" source files
SRC?=./src/nativecode/test.c
RAWDRAWANDROIDSRCS=./src/nativecode/android_native_app_glue.c
ANDROIDSRCS:= $(SRC) $(RAWDRAWANDROIDSRCS)
# symlink
./app.apk: ./result/app.apk
ln -sfrv ./result/app.apk ./app.apk
# zipalign and sign again (second signing)
./result/app.apk : ./result/signed.apk app-config.sh ./keystore
./result/app.apk : ./result/unsigned.apk app-config.sh ./keystore
$(BUILDTOOLS)/zipalign -v -f 4 $< $@
$(BUILDTOOLS)/apksigner sign --ks keystore --key-pass pass:armena --ks-pass pass:armena $@
# sign the apk file (first sign)
./result/signed.apk : ./result/unsigned.apk ./keystore
jarsigner -verbose -keystore ./keystore -storepass armena -keypass armena -signedjar $@ $< helljniKey
## sign the apk file (first sign)
#./result/signed.apk : ./result/unsigned.apk ./keystore
# jarsigner -verbose -keystore ./keystore -storepass armena -keypass armena -signedjar $@ $< helljniKey
# make a "keystore" for the cryptographic signing stuff
./keystore :
keytool -genkeypair -validity 1000 -dname "CN=alexander,O=Android,C=JPN" -keystore $@ \
keytool -genkeypair -validity 2000 -dname "CN=$(APP_PACKAGE),O=Android,C=IT" -keystore $@ \
-storepass armena -keypass armena -alias helljniKey -keyalg RSA -v
# aapt "package" together the dalvik/hex stuff (and "assets" and "res")
./result/unsigned.apk : ./result/bin/classes.dex ./assets ./AndroidManifest.xml
./result/unsigned.apk : ./assets ./AndroidManifest.xml ./result/bin/lib/arm64-v8a/libnativecode.so $(shell find ./res -type f)
rm -rvf "$@"
$(BUILDTOOLS)/aapt package \
-v -u -f -M ./AndroidManifest.xml -S ./res \
-I $(ANDROID_JAR) -A ./assets -F $@ ./result/bin
# convert "java class"es files (i.e bytecode) to dalvic/d8 android thing
./result/bin/classes.dex : ./obj/$(PACKAGE)/AppActivity.class
mkdir -p ./result/bin
$(BUILDTOOLS)/d8 ./obj/$(PACKAGE)/*.class \
--lib $(ANDROID_JAR) --output ./result/bin
supi:
echo $(shell find ./res -type f)
# compile (javac) the class from
./obj/$(PACKAGE)/AppActivity.class : ./src/$(PACKAGE)/AppActivity.java ./src/$(PACKAGE)/R.java
mkdir -p ./obj/$(PACKAGE)
javac -d ./obj -classpath $(ANDROID_JAR) -sourcepath ./src $<
alex: ./result/bin/classes.dex
echo $< > $@
## convert "java class"es files (i.e bytecode) to dalvic/d8 android thing
#./result/bin/classes.dex : ./obj/$(PACKAGE)/AppActivity.class
# mkdir -p ./result/bin
# $(BUILDTOOLS)/d8 ./obj/$(PACKAGE)/*.class \
# --lib $(ANDROID_JAR) --output ./result/bin; RESULT=$$?; echo result was $$RESULT; (exit $$RESULT)
# make the resources "R.java" thing
./src/$(PACKAGE)/R.java : $(shell find ./res -type f) app-config.sh ./AndroidManifest.xml ./android-sdk/installed | ./src/$(PACKAGE)
$(BUILDTOOLS)/aapt package \
-v -f -m -S ./res -J ./src -M ./AndroidManifest.xml \
-I $(ANDROID_JAR)
./result/bin/lib/arm64-v8a/libnativecode.so: $(ANDROIDSRCS)
mkdir -p "$$(dirname "$@")"
$(CC_ARM64) $(CFLAGS) $(CFLAGS_ARM64) -o $@ $^ -L$(NDK_TOOLCHAIN)/sysroot/usr/lib/aarch64-linux-android/$(ANDROIDVERSION) $(LDFLAGS)
## compile (javac) the class from
#./obj/$(PACKAGE)/AppActivity.class : ./src/$(PACKAGE)/AppActivity.java ./src/$(PACKAGE)/R.java
# mkdir -p ./obj/$(PACKAGE)
# javac -d ./obj -classpath $(ANDROID_JAR) -sourcepath ./src $<
#
#
## make the resources "R.java" thing
#./src/$(PACKAGE)/R.java : $(shell find ./res -type f) app-config.sh ./AndroidManifest.xml ./android-sdk/installed | ./src/$(PACKAGE)
# $(BUILDTOOLS)/aapt package \
# -v -f -m -S ./res -J ./src -M ./AndroidManifest.xml \
# -I $(ANDROID_JAR)
# generate the AppActivity.java (template
# the "|" denotes an "order-only" prerequiste (as in https://stackoverflow.com/a/58040049/1711186)
@ -61,19 +101,21 @@ include Makefile.app-config
mkdir -p $@
# install the necessary android sdks
./android-sdk/installed: app-config.sh
./android-sdk/installed: app-config.sh ./.Makefile.scripts/make--android-sdk.sh
./.Makefile.scripts/make--android-sdk.sh
# generate the AndroidManifest.xml
./AndroidManifest.xml: app-config.sh
./AndroidManifest.xml: app-config.sh ./.Makefile.scripts/make--AndroidManifest.xml
./.Makefile.scripts/make--AndroidManifest.xml
Makefile.app-config: app-config.sh Makefile
source app-config.sh; \
tee $@ << MAKEFILE_APP_CONFIG
BUILDTOOLS:=$${ANDROID_SDK_ROOT}/$$(tr ';' '/' < android-sdk/.installed.buildtools.version.current)
ANDROID_JAR:=$${ANDROID_SDK_ROOT}/$$(tr ';' '/' < android-sdk/.installed.platforms.version.current)/android.jar
ANDROIDVERSION:=$$APP_VERSION_SDK_TARGET;
BUILDTOOLS:=$${ANDROID_SDK_ROOT}/build-tools/29.0.3/
ANDROID_JAR:=$${ANDROID_SDK_ROOT}/platforms/android-29/android.jar
PACKAGE:=$$(echo "$$APP_PACKAGE" | tr '.' '/')
APP_PACKAGE=$${APP_PACKAGE}
MAKEFILE_APP_CONFIG
# use whiptail textgui to make configuration (android API level, app-name, app-label etc...)

View file

@ -18,23 +18,72 @@
</style>
<body>
<h1>Webview</h1>
Test page
<br>
<div ID=counter1></div>
<br>
<div ID=counter2></div>
<noscript>
<h2> no javascript </h2>
</noscript>
<script>
let i = 0;
var webMessagePort;
function SendMessageToC() {
if( webMessagePort )
{
webMessagePort.postMessage( "js->c: " + i );
document.getElementById( "counter1" ).innerHTML = i++;
}
else
{
document.getElementById( "counter1" ).innerHTML = "no port connected";
}
}
onmessage = function (e) {
webMessagePort = e.ports[0];
webMessagePort.onmessage = function (f)
{
document.getElementById( "counter2" ).innerHTML = f.data;
}
}
</script>
<script>
window.addEventListener("load",()=>{
document.body.style.backgroundColor="#ffa";
},false);
setTimeout(()=>{
window.addEventListener("error",(error)=>{
document.body.innerHTML = "<h1>error</h1><pre>" +
document.body.innerHTML = "<h1>aerror</h1><pre>" +
error.filename +
"\nline:" + error.lineno +
"\n"+error.message +"</pre>";
},false);
},10000);
window.addEventListener("load",()=>{
var count = localStorage.getItem("app-opened-count")|| 0;
count++;
localStorage.setItem("app-opened-count",count);
var h2 = document.createElement("h2");
h2.textContent = "Javascript works! (app was opened " + count + " times)";
h2.style.animation="wobble 1s ease-in-out 0s 1 forwards normal running"
document.body.appendChild(h2);
},false);
// window.addEventListener("load",()=>{
// var count;
// if( localStorage){
// count = localStorage.getItem("app-opened-count")|| 0;
// count++;
// localStorage.setItem("app-opened-count",count);
// } else {
// count = "NO LOCALSTORAGE SETUP"
// }
// var h2 = document.createElement("h2");
// h2.textContent = "Javascript works! (app was opened " + count + " times)";
// h2.style.animation="wobble 1s ease-in-out 0s 1 forwards normal running"
// document.body.appendChild(h2);
// },false);
</script>
</body>
</html>

View file

@ -6,4 +6,10 @@
<path
android:pathData="M95 50A45 45 0 0 1 5 50A45 45 0 0 1 95 50Z"
android:fillColor="#FF0000" />
<path
android:pathData="M75 50A25 25 0 0 1 5 50A25 25 0 0 1 75 50Z"
android:fillColor="#FFFF00" />
<path
android:pathData="M60 50A10 10 0 0 1 5 50A10 10 0 0 1 60 50Z"
android:fillColor="#0000EE" />
</vector>

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 <>< CNLohr
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,526 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include <jni.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/resource.h>
#include "android_native_app_glue.h"
#include <android/log.h>
#include <android/native_activity.h>
#include "webview_native_activity.h"
struct android_app * gapp;
#define LOGI(...) ((void)printf(__VA_ARGS__))
#define LOGE(...) ((void)printf(__VA_ARGS__))
/* For debug builds, always enable the debug traces in this library */
#ifndef NDEBUG
# define LOGV(...) ((void)printf(__VA_ARGS__))
#else
# define LOGV(...) ((void)0)
#endif
typedef struct __attribute__((packed))
{
void (*callback)( void * );
void * opaque;
} MainThreadCallbackProps;
static int pfd[2];
pthread_t debug_capture_thread;
static void * debug_capture_thread_fn( void * v )
{
//struct android_app * app = (struct android_app*)v;
ssize_t readSize;
char buf[2048];
while((readSize = read(pfd[0], buf, sizeof buf - 1)) > 0) {
if(buf[readSize - 1] == '\n') {
--readSize;
}
buf[readSize] = 0; // add null-terminator
__android_log_write(ANDROID_LOG_DEBUG, APP_PACKAGE, buf); // Set any log level you want
#ifdef RDALOGFNCB
extern void RDALOGFNCB( int size, char * buf );
RDALOGFNCB( readSize, buf );
#endif
//if( debug_capture_hook_function ) debug_capture_hook_function( readSize, buf );
}
return 0;
}
static void free_saved_state(struct android_app* android_app) {
pthread_mutex_lock(&android_app->mutex);
if (android_app->savedState != NULL) {
free(android_app->savedState);
android_app->savedState = NULL;
android_app->savedStateSize = 0;
}
pthread_mutex_unlock(&android_app->mutex);
}
int8_t android_app_read_cmd(struct android_app* android_app) {
int8_t cmd;
if (read(android_app->msgread, &cmd, sizeof(cmd)) == sizeof(cmd)) {
switch (cmd) {
case APP_CMD_SAVE_STATE:
free_saved_state(android_app);
break;
}
return cmd;
} else {
LOGE("No data on command pipe!");
}
return -1;
}
static void print_cur_config(struct android_app* android_app) {
//For additional debugging this can be enabled, but for now - no need for the extra space.
/*
char lang[2], country[2];
AConfiguration_getLanguage(android_app->config, lang);
AConfiguration_getCountry(android_app->config, country);
LOGV("Config: mcc=%d mnc=%d lang=%c%c cnt=%c%c orien=%d touch=%d dens=%d "
"keys=%d nav=%d keysHid=%d navHid=%d sdk=%d size=%d long=%d "
"modetype=%d modenight=%d",
AConfiguration_getMcc(android_app->config),
AConfiguration_getMnc(android_app->config),
lang[0], lang[1], country[0], country[1],
AConfiguration_getOrientation(android_app->config),
AConfiguration_getTouchscreen(android_app->config),
AConfiguration_getDensity(android_app->config),
AConfiguration_getKeyboard(android_app->config),
AConfiguration_getNavigation(android_app->config),
AConfiguration_getKeysHidden(android_app->config),
AConfiguration_getNavHidden(android_app->config),
AConfiguration_getSdkVersion(android_app->config),
AConfiguration_getScreenSize(android_app->config),
AConfiguration_getScreenLong(android_app->config),
AConfiguration_getUiModeType(android_app->config),
AConfiguration_getUiModeNight(android_app->config));
*/
}
void android_app_pre_exec_cmd(struct android_app* android_app, int8_t cmd) {
switch (cmd) {
case APP_CMD_INPUT_CHANGED:
LOGV("APP_CMD_INPUT_CHANGED\n");
pthread_mutex_lock(&android_app->mutex);
if (android_app->inputQueue != NULL) {
AInputQueue_detachLooper(android_app->inputQueue);
}
android_app->inputQueue = android_app->pendingInputQueue;
if (android_app->inputQueue != NULL) {
LOGV("Attaching input queue to looper");
AInputQueue_attachLooper(android_app->inputQueue,
android_app->looper, LOOPER_ID_INPUT, NULL,
&android_app->inputPollSource);
}
pthread_cond_broadcast(&android_app->cond);
pthread_mutex_unlock(&android_app->mutex);
break;
case APP_CMD_INIT_WINDOW:
LOGV("APP_CMD_INIT_WINDOW\n");
pthread_mutex_lock(&android_app->mutex);
android_app->window = android_app->pendingWindow;
pthread_cond_broadcast(&android_app->cond);
pthread_mutex_unlock(&android_app->mutex);
break;
case APP_CMD_TERM_WINDOW:
LOGV("APP_CMD_TERM_WINDOW\n");
pthread_cond_broadcast(&android_app->cond);
break;
case APP_CMD_RESUME:
case APP_CMD_START:
case APP_CMD_PAUSE:
case APP_CMD_STOP:
LOGV("activityState=%d\n", cmd);
pthread_mutex_lock(&android_app->mutex);
android_app->activityState = cmd;
pthread_cond_broadcast(&android_app->cond);
pthread_mutex_unlock(&android_app->mutex);
break;
case APP_CMD_CONFIG_CHANGED:
LOGV("APP_CMD_CONFIG_CHANGED\n");
AConfiguration_fromAssetManager(android_app->config,
android_app->activity->assetManager);
print_cur_config(android_app);
break;
case APP_CMD_DESTROY:
LOGV("APP_CMD_DESTROY\n");
android_app->destroyRequested = 1;
break;
}
}
void android_app_post_exec_cmd(struct android_app* android_app, int8_t cmd) {
switch (cmd) {
case APP_CMD_TERM_WINDOW:
LOGV("APP_CMD_TERM_WINDOW\n");
pthread_mutex_lock(&android_app->mutex);
android_app->window = NULL;
pthread_cond_broadcast(&android_app->cond);
pthread_mutex_unlock(&android_app->mutex);
break;
case APP_CMD_SAVE_STATE:
LOGV("APP_CMD_SAVE_STATE\n");
pthread_mutex_lock(&android_app->mutex);
android_app->stateSaved = 1;
pthread_cond_broadcast(&android_app->cond);
pthread_mutex_unlock(&android_app->mutex);
break;
case APP_CMD_RESUME:
free_saved_state(android_app);
break;
}
}
void app_dummy() {
}
static void android_app_destroy(struct android_app* android_app) {
LOGV("android_app_destroy!");
free_saved_state(android_app);
pthread_mutex_lock(&android_app->mutex);
if (android_app->inputQueue != NULL) {
AInputQueue_detachLooper(android_app->inputQueue);
}
AConfiguration_delete(android_app->config);
android_app->destroyed = 1;
pthread_cond_broadcast(&android_app->cond);
pthread_mutex_unlock(&android_app->mutex);
// Can't touch android_app object after this.
}
static void process_input(struct android_app* app, struct android_poll_source* source) {
AInputEvent* event = NULL;
while (AInputQueue_getEvent(app->inputQueue, &event) >= 0) {
//LOGV("New input event: type=%d\n", AInputEvent_getType(event));
if (AInputQueue_preDispatchEvent(app->inputQueue, event)) {
continue;
}
int32_t handled = 0;
if (app->onInputEvent != NULL) handled = app->onInputEvent(app, event);
AInputQueue_finishEvent(app->inputQueue, event, handled);
}
}
static int process_ui( int dummy1, int dummy2, void * dummy3 ) {
// Can't trust parameters in UI thread callback.
MainThreadCallbackProps rep;
read(gapp->uimsgread, &rep, sizeof(rep));
rep.callback( rep.opaque );
return 1;
}
static void process_cmd(struct android_app* app, struct android_poll_source* source) {
int8_t cmd = android_app_read_cmd(app);
android_app_pre_exec_cmd(app, cmd);
if (app->onAppCmd != NULL) app->onAppCmd(app, cmd);
android_app_post_exec_cmd(app, cmd);
}
static void* android_app_entry(void* param) {
struct android_app* android_app = (struct android_app*)param;
android_app->config = AConfiguration_new();
AConfiguration_fromAssetManager(android_app->config, android_app->activity->assetManager);
print_cur_config(android_app);
android_app->cmdPollSource.id = LOOPER_ID_MAIN;
android_app->cmdPollSource.app = android_app;
android_app->cmdPollSource.process = process_cmd;
android_app->inputPollSource.id = LOOPER_ID_INPUT;
android_app->inputPollSource.app = android_app;
android_app->inputPollSource.process = process_input;
ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
ALooper_addFd(looper, android_app->msgread, LOOPER_ID_MAIN, ALOOPER_EVENT_INPUT, NULL, &android_app->cmdPollSource);
android_app->looper = looper;
pthread_mutex_lock(&android_app->mutex);
android_app->running = 1;
pthread_cond_broadcast(&android_app->cond);
pthread_mutex_unlock(&android_app->mutex);
android_main(android_app);
android_app_destroy(android_app);
return NULL;
}
// --------------------------------------------------------------------
// Native activity interaction (called from main thread)
// --------------------------------------------------------------------
static struct android_app* android_app_create(ANativeActivity* activity,
void* savedState, size_t savedStateSize) {
struct android_app* android_app = (struct android_app*)malloc(sizeof(struct android_app));
memset(android_app, 0, sizeof(struct android_app));
android_app->activity = activity;
pthread_mutex_init(&android_app->mutex, NULL);
pthread_cond_init(&android_app->cond, NULL);
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
//Capture input
setvbuf(stdout, 0, _IOLBF, 0); // make stdout line-buffered
setvbuf(stderr, 0, _IONBF, 0); // make stderr unbuffered
pipe(pfd);
dup2(pfd[1], 1);
dup2(pfd[1], 2);
pthread_create(&debug_capture_thread, &attr, debug_capture_thread_fn, android_app);
if (savedState != NULL) {
android_app->savedState = malloc(savedStateSize);
android_app->savedStateSize = savedStateSize;
memcpy(android_app->savedState, savedState, savedStateSize);
}
int msgpipe[2];
if (pipe(msgpipe)) {
LOGE("could not create pipe: %s", strerror(errno));
return NULL;
}
android_app->msgread = msgpipe[0];
android_app->msgwrite = msgpipe[1];
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Handle calling events on the UI thread. You can get callbacks with RunCallbackOnUIThread.
int msgpipemain[2];
if (pipe(msgpipemain)) {
LOGE("could not create pipe: %s", strerror(errno));
return NULL;
}
android_app->uimsgread = msgpipemain[0];
android_app->uimsgwrite = msgpipemain[1];
ALooper * looper = ALooper_forThread();
ALooper_addFd(looper, android_app->uimsgread, LOOPER_ID_MAIN_THREAD, ALOOPER_EVENT_INPUT, process_ui, gapp); //NOTE: Cannot use NULL callback
android_app->looperui = looper;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&android_app->thread, &attr, android_app_entry, android_app);
// Wait for thread to start.
pthread_mutex_lock(&android_app->mutex);
while (!android_app->running) {
pthread_cond_wait(&android_app->cond, &android_app->mutex);
}
pthread_mutex_unlock(&android_app->mutex);
return android_app;
}
static void android_app_write_cmd(struct android_app* android_app, int8_t cmd) {
if (write(android_app->msgwrite, &cmd, sizeof(cmd)) != sizeof(cmd)) {
LOGE("Failure writing android_app cmd: %s\n", strerror(errno));
}
}
static void android_app_set_input(struct android_app* android_app, AInputQueue* inputQueue) {
pthread_mutex_lock(&android_app->mutex);
android_app->pendingInputQueue = inputQueue;
android_app_write_cmd(android_app, APP_CMD_INPUT_CHANGED);
while (android_app->inputQueue != android_app->pendingInputQueue) {
pthread_cond_wait(&android_app->cond, &android_app->mutex);
}
pthread_mutex_unlock(&android_app->mutex);
}
static void android_app_set_window(struct android_app* android_app, ANativeWindow* window) {
pthread_mutex_lock(&android_app->mutex);
if (android_app->pendingWindow != NULL) {
android_app_write_cmd(android_app, APP_CMD_TERM_WINDOW);
}
android_app->pendingWindow = window;
if (window != NULL) {
android_app_write_cmd(android_app, APP_CMD_INIT_WINDOW);
}
while (android_app->window != android_app->pendingWindow) {
pthread_cond_wait(&android_app->cond, &android_app->mutex);
}
pthread_mutex_unlock(&android_app->mutex);
}
static void android_app_set_activity_state(struct android_app* android_app, int8_t cmd) {
pthread_mutex_lock(&android_app->mutex);
android_app_write_cmd(android_app, cmd);
while (android_app->activityState != cmd) {
pthread_cond_wait(&android_app->cond, &android_app->mutex);
}
pthread_mutex_unlock(&android_app->mutex);
}
static void android_app_free(struct android_app* android_app) {
pthread_mutex_lock(&android_app->mutex);
android_app_write_cmd(android_app, APP_CMD_DESTROY);
while (!android_app->destroyed) {
pthread_cond_wait(&android_app->cond, &android_app->mutex);
}
pthread_mutex_unlock(&android_app->mutex);
close(android_app->msgread);
close(android_app->msgwrite);
pthread_cond_destroy(&android_app->cond);
pthread_mutex_destroy(&android_app->mutex);
free(android_app);
}
static void onDestroy(ANativeActivity* activity) {
LOGV("Destroy: %p\n", activity);
android_app_free((struct android_app*)activity->instance);
}
static void onStart(ANativeActivity* activity) {
LOGV("Start: %p\n", activity);
android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_START);
}
static void onResume(ANativeActivity* activity) {
LOGV("Resume: %p\n", activity);
android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_RESUME);
}
static void* onSaveInstanceState(ANativeActivity* activity, size_t* outLen) {
struct android_app* android_app = (struct android_app*)activity->instance;
void* savedState = NULL;
LOGV("SaveInstanceState: %p\n", activity);
pthread_mutex_lock(&android_app->mutex);
android_app->stateSaved = 0;
android_app_write_cmd(android_app, APP_CMD_SAVE_STATE);
while (!android_app->stateSaved) {
pthread_cond_wait(&android_app->cond, &android_app->mutex);
}
if (android_app->savedState != NULL) {
savedState = android_app->savedState;
*outLen = android_app->savedStateSize;
android_app->savedState = NULL;
android_app->savedStateSize = 0;
}
pthread_mutex_unlock(&android_app->mutex);
return savedState;
}
static void onPause(ANativeActivity* activity) {
LOGV("Pause: %p\n", activity);
android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_PAUSE);
}
static void onStop(ANativeActivity* activity) {
LOGV("Stop: %p\n", activity);
android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_STOP);
}
static void onConfigurationChanged(ANativeActivity* activity) {
struct android_app* android_app = (struct android_app*)activity->instance;
LOGV("ConfigurationChanged: %p\n", activity);
android_app_write_cmd(android_app, APP_CMD_CONFIG_CHANGED);
}
static void onLowMemory(ANativeActivity* activity) {
struct android_app* android_app = (struct android_app*)activity->instance;
LOGV("LowMemory: %p\n", activity);
android_app_write_cmd(android_app, APP_CMD_LOW_MEMORY);
}
static void onWindowFocusChanged(ANativeActivity* activity, int focused) {
LOGV("WindowFocusChanged: %p -- %d\n", activity, focused);
android_app_write_cmd((struct android_app*)activity->instance,
focused ? APP_CMD_GAINED_FOCUS : APP_CMD_LOST_FOCUS);
}
static void onNativeWindowCreated(ANativeActivity* activity, ANativeWindow* window) {
LOGV("NativeWindowCreated: %p -- %p\n", activity, window);
android_app_set_window((struct android_app*)activity->instance, window);
}
static void onNativeWindowDestroyed(ANativeActivity* activity, ANativeWindow* window) {
LOGV("NativeWindowDestroyed: %p -- %p\n", activity, window);
android_app_set_window((struct android_app*)activity->instance, NULL);
}
static void onInputQueueCreated(ANativeActivity* activity, AInputQueue* queue) {
LOGV("InputQueueCreated: %p -- %p\n", activity, queue);
android_app_set_input((struct android_app*)activity->instance, queue);
}
static void onInputQueueDestroyed(ANativeActivity* activity, AInputQueue* queue) {
LOGV("InputQueueDestroyed: %p -- %p\n", activity, queue);
android_app_set_input((struct android_app*)activity->instance, NULL);
}
static void onNativeWindowRedrawNeeded(ANativeActivity* activity, ANativeWindow *window ) {
LOGV("onNativeWindowRedrawNeeded: %p -- %p\n", activity, window);
}
JNIEXPORT
void ANativeActivity_onCreate(ANativeActivity* activity, void* savedState,
size_t savedStateSize) {
LOGV("Creating: %p\n", activity);
activity->callbacks->onDestroy = onDestroy;
activity->callbacks->onStart = onStart;
activity->callbacks->onResume = onResume;
activity->callbacks->onSaveInstanceState = onSaveInstanceState;
activity->callbacks->onPause = onPause;
activity->callbacks->onStop = onStop;
activity->callbacks->onConfigurationChanged = onConfigurationChanged;
activity->callbacks->onLowMemory = onLowMemory;
activity->callbacks->onWindowFocusChanged = onWindowFocusChanged;
activity->callbacks->onNativeWindowCreated = onNativeWindowCreated;
activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed;
activity->callbacks->onInputQueueCreated = onInputQueueCreated;
activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed;
activity->callbacks->onNativeWindowRedrawNeeded = onNativeWindowRedrawNeeded;
activity->instance = android_app_create(activity, savedState, savedStateSize);
}
void RunCallbackOnUIThread( void (*callback)(void *), void * opaque )
{
MainThreadCallbackProps gpdata;
gpdata.callback = callback;
gpdata.opaque = opaque;
write(gapp->uimsgwrite, &gpdata, sizeof(gpdata) );
}

View file

@ -0,0 +1,376 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef _ANDROID_NATIVE_APP_GLUE_H
#define _ANDROID_NATIVE_APP_GLUE_H
#include <poll.h>
#include <pthread.h>
#include <sched.h>
#include <android/configuration.h>
#include <android/looper.h>
#include <android/native_activity.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* The native activity interface provided by <android/native_activity.h>
* is based on a set of application-provided callbacks that will be called
* by the Activity's main thread when certain events occur.
*
* This means that each one of this callbacks _should_ _not_ block, or they
* risk having the system force-close the application. This programming
* model is direct, lightweight, but constraining.
*
* The 'android_native_app_glue' static library is used to provide a different
* execution model where the application can implement its own main event
* loop in a different thread instead. Here's how it works:
*
* 1/ The application must provide a function named "android_main()" that
* will be called when the activity is created, in a new thread that is
* distinct from the activity's main thread.
*
* 2/ android_main() receives a pointer to a valid "android_app" structure
* that contains references to other important objects, e.g. the
* ANativeActivity object instance the application is running in.
*
* 3/ the "android_app" object holds an ALooper instance that already
* listens to two important things:
*
* - activity lifecycle events (e.g. "pause", "resume"). See APP_CMD_XXX
* declarations below.
*
* - input events coming from the AInputQueue attached to the activity.
*
* Each of these correspond to an ALooper identifier returned by
* ALooper_pollOnce with values of LOOPER_ID_MAIN and LOOPER_ID_INPUT,
* respectively.
*
* Your application can use the same ALooper to listen to additional
* file-descriptors. They can either be callback based, or with return
* identifiers starting with LOOPER_ID_USER.
*
* 4/ Whenever you receive a LOOPER_ID_MAIN or LOOPER_ID_INPUT event,
* the returned data will point to an android_poll_source structure. You
* can call the process() function on it, and fill in android_app->onAppCmd
* and android_app->onInputEvent to be called for your own processing
* of the event.
*
* Alternatively, you can call the low-level functions to read and process
* the data directly... look at the process_cmd() and process_input()
* implementations in the glue to see how to do this.
*
* See the sample named "native-activity" that comes with the NDK with a
* full usage example. Also look at the JavaDoc of NativeActivity.
*/
struct android_app;
/**
* Data associated with an ALooper fd that will be returned as the "outData"
* when that source has data ready.
*/
struct android_poll_source {
// The identifier of this source. May be LOOPER_ID_MAIN or
// LOOPER_ID_INPUT.
int32_t id;
// The android_app this ident is associated with.
struct android_app* app;
// Function to call to perform the standard processing of data from
// this source.
void (*process)(struct android_app* app, struct android_poll_source* source);
};
/**
* This is the interface for the standard glue code of a threaded
* application. In this model, the application's code is running
* in its own thread separate from the main thread of the process.
* It is not required that this thread be associated with the Java
* VM, although it will need to be in order to make JNI calls any
* Java objects.
*/
struct android_app {
// The application can place a pointer to its own state object
// here if it likes.
void* userData;
// Fill this in with the function to process main app commands (APP_CMD_*)
void (*onAppCmd)(struct android_app* app, int32_t cmd);
// Fill this in with the function to process input events. At this point
// the event has already been pre-dispatched, and it will be finished upon
// return. Return 1 if you have handled the event, 0 for any default
// dispatching.
int32_t (*onInputEvent)(struct android_app* app, AInputEvent* event);
// The ANativeActivity object instance that this app is running in.
ANativeActivity* activity;
// The current configuration the app is running in.
AConfiguration* config;
// This is the last instance's saved state, as provided at creation time.
// It is NULL if there was no state. You can use this as you need; the
// memory will remain around until you call android_app_exec_cmd() for
// APP_CMD_RESUME, at which point it will be freed and savedState set to NULL.
// These variables should only be changed when processing a APP_CMD_SAVE_STATE,
// at which point they will be initialized to NULL and you can malloc your
// state and place the information here. In that case the memory will be
// freed for you later.
void* savedState;
size_t savedStateSize;
// The ALooper associated with the app's thread.
ALooper* looper;
// The ALooper associated with the main thread.
ALooper* looperui;
// When non-NULL, this is the input queue from which the app will
// receive user input events.
AInputQueue* inputQueue;
// When non-NULL, this is the window surface that the app can draw in.
ANativeWindow* window;
// Current content rectangle of the window; this is the area where the
// window's content should be placed to be seen by the user.
ARect contentRect;
// Current state of the app's activity. May be either APP_CMD_START,
// APP_CMD_RESUME, APP_CMD_PAUSE, or APP_CMD_STOP; see below.
int activityState;
// This is non-zero when the application's NativeActivity is being
// destroyed and waiting for the app thread to complete.
int destroyRequested;
// -------------------------------------------------
// Below are "private" implementation of the glue code.
pthread_mutex_t mutex;
pthread_cond_t cond;
int msgread;
int msgwrite;
int uimsgread;
int uimsgwrite;
pthread_t thread;
struct android_poll_source cmdPollSource;
struct android_poll_source inputPollSource;
int running;
int stateSaved;
int destroyed;
int redrawNeeded;
AInputQueue* pendingInputQueue;
ANativeWindow* pendingWindow;
ARect pendingContentRect;
};
enum {
/**
* Looper data ID of commands coming from the app's main thread, which
* is returned as an identifier from ALooper_pollOnce(). The data for this
* identifier is a pointer to an android_poll_source structure.
* These can be retrieved and processed with android_app_read_cmd()
* and android_app_exec_cmd().
*/
LOOPER_ID_MAIN = 1,
/**
* Looper data ID of events coming from the AInputQueue of the
* application's window, which is returned as an identifier from
* ALooper_pollOnce(). The data for this identifier is a pointer to an
* android_poll_source structure. These can be read via the inputQueue
* object of android_app.
*/
LOOPER_ID_INPUT = 2,
/**
* Start of main ALooper identifiers.
*/
LOOPER_ID_MAIN_THREAD = 3,
/**
* Start of user-defined ALooper identifiers.
*/
LOOPER_ID_USER = 4,
};
enum {
/**
* Command from main thread: the AInputQueue has changed. Upon processing
* this command, android_app->inputQueue will be updated to the new queue
* (or NULL).
*/
APP_CMD_INPUT_CHANGED,
/**
* Command from main thread: a new ANativeWindow is ready for use. Upon
* receiving this command, android_app->window will contain the new window
* surface.
*/
APP_CMD_INIT_WINDOW,
/**
* Command from main thread: the existing ANativeWindow needs to be
* terminated. Upon receiving this command, android_app->window still
* contains the existing window; after calling android_app_exec_cmd
* it will be set to NULL.
*/
APP_CMD_TERM_WINDOW,
/**
* Command from main thread: the current ANativeWindow has been resized.
* Please redraw with its new size.
*/
APP_CMD_WINDOW_RESIZED,
/**
* Command from main thread: the system needs that the current ANativeWindow
* be redrawn. You should redraw the window before handing this to
* android_app_exec_cmd() in order to avoid transient drawing glitches.
*/
APP_CMD_WINDOW_REDRAW_NEEDED,
/**
* Command from main thread: the content area of the window has changed,
* such as from the soft input window being shown or hidden. You can
* find the new content rect in android_app::contentRect.
*/
APP_CMD_CONTENT_RECT_CHANGED,
/**
* Command from main thread: the app's activity window has gained
* input focus.
*/
APP_CMD_GAINED_FOCUS,
/**
* Command from main thread: the app's activity window has lost
* input focus.
*/
APP_CMD_LOST_FOCUS,
/**
* Command from main thread: the current device configuration has changed.
*/
APP_CMD_CONFIG_CHANGED,
/**
* Command from main thread: the system is running low on memory.
* Try to reduce your memory use.
*/
APP_CMD_LOW_MEMORY,
/**
* Command from main thread: the app's activity has been started.
*/
APP_CMD_START,
/**
* Command from main thread: the app's activity has been resumed.
*/
APP_CMD_RESUME,
/**
* Command from main thread: the app should generate a new saved state
* for itself, to restore from later if needed. If you have saved state,
* allocate it with malloc and place it in android_app.savedState with
* the size in android_app.savedStateSize. The will be freed for you
* later.
*/
APP_CMD_SAVE_STATE,
/**
* Command from main thread: the app's activity has been paused.
*/
APP_CMD_PAUSE,
/**
* Command from main thread: the app's activity has been stopped.
*/
APP_CMD_STOP,
/**
* Command from main thread: the app's activity is being destroyed,
* and waiting for the app thread to clean up and exit before proceeding.
*/
APP_CMD_DESTROY,
/**
* Custom command to execute something from an event queue
*/
APP_CMD_CUSTOM_EVENT,
};
/**
* Call when ALooper_pollAll() returns LOOPER_ID_MAIN, reading the next
* app command message.
*/
int8_t android_app_read_cmd(struct android_app* android_app);
/**
* Call with the command returned by android_app_read_cmd() to do the
* initial pre-processing of the given command. You can perform your own
* actions for the command after calling this function.
*/
void android_app_pre_exec_cmd(struct android_app* android_app, int8_t cmd);
/**
* Call with the command returned by android_app_read_cmd() to do the
* final post-processing of the given command. You must have done your own
* actions for the command before calling this function.
*/
void android_app_post_exec_cmd(struct android_app* android_app, int8_t cmd);
/**
* Dummy function that used to be used to prevent the linker from stripping app
* glue code. No longer necessary, since __attribute__((visibility("default")))
* does this for us.
*/
__attribute__((
deprecated("Calls to app_dummy are no longer necessary. See "
"https://github.com/android-ndk/ndk/issues/381."))) void
app_dummy();
/**
* This is the function that application code must implement, representing
* the main entry to the app.
*/
extern void android_main(struct android_app* app);
/**
* Mechanism to run code on main UI thread.
*/
void RunCallbackOnUIThread( void (*callback)(void *), void * opaque );
#ifdef __cplusplus
}
#endif
#endif /* _ANDROID_NATIVE_APP_GLUE_H */

View file

@ -0,0 +1,210 @@
//Copyright 2020 <>< Charles Lohr, You may use this file and library freely under the MIT/x11, NewBSD or ColorChord Licenses.
#include "android_usb_devices.h"
#include "CNFG.h"
#include "os_generic.h"
double dTimeOfUSBFail;
double dTimeOfLastAsk;
jobject deviceConnection = 0;
int deviceConnectionFD = 0;
extern struct android_app * gapp;
void DisconnectUSB()
{
deviceConnectionFD = 0;
dTimeOfUSBFail = OGGetAbsoluteTime();
}
int RequestPermissionOrGetConnectionFD( char * ats, uint16_t vid, uint16_t pid )
{
//Don't permit
if( OGGetAbsoluteTime() - dTimeOfUSBFail < 1 )
{
ats+=sprintf(ats, "Comms failed. Waiting to reconnect." );
return -1;
}
struct android_app* app = gapp;
const struct JNINativeInterface * env = 0;
const struct JNINativeInterface ** envptr = &env;
const struct JNIInvokeInterface ** jniiptr = app->activity->vm;
const struct JNIInvokeInterface * jnii = *jniiptr;
jnii->AttachCurrentThread( jniiptr, &envptr, NULL);
env = (*envptr);
// Retrieves NativeActivity.
jobject lNativeActivity = gapp->activity->clazz;
//https://stackoverflow.com/questions/13280581/using-android-to-communicate-with-a-usb-hid-device
//UsbManager manager = (UsbManager)getSystemService(Context.USB_SERVICE);
jclass ClassContext = env->FindClass( envptr, "android/content/Context" );
jfieldID lid_USB_SERVICE = env->GetStaticFieldID( envptr, ClassContext, "USB_SERVICE", "Ljava/lang/String;" );
jobject USB_SERVICE = env->GetStaticObjectField( envptr, ClassContext, lid_USB_SERVICE );
jmethodID MethodgetSystemService = env->GetMethodID( envptr, ClassContext, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;" );
jobject manager = env->CallObjectMethod( envptr, lNativeActivity, MethodgetSystemService, USB_SERVICE);
//Actually returns an android/hardware/usb/UsbManager
jclass ClassUsbManager = env->FindClass( envptr, "android/hardware/usb/UsbManager" );
//HashMap<String, UsbDevice> deviceList = mManager.getDeviceList();
jmethodID MethodgetDeviceList = env->GetMethodID( envptr, ClassUsbManager, "getDeviceList", "()Ljava/util/HashMap;" );
jobject deviceList = env->CallObjectMethod( envptr, manager, MethodgetDeviceList );
//Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
jclass ClassHashMap = env->FindClass( envptr, "java/util/HashMap" );
jmethodID Methodvalues = env->GetMethodID( envptr, ClassHashMap, "values", "()Ljava/util/Collection;" );
jobject deviceListCollection = env->CallObjectMethod( envptr, deviceList, Methodvalues );
jclass ClassCollection = env->FindClass( envptr, "java/util/Collection" );
jmethodID Methoditerator = env->GetMethodID( envptr, ClassCollection, "iterator", "()Ljava/util/Iterator;" );
jobject deviceListIterator = env->CallObjectMethod( envptr, deviceListCollection, Methoditerator );
jclass ClassIterator = env->FindClass( envptr, "java/util/Iterator" );
//while (deviceIterator.hasNext())
jmethodID MethodhasNext = env->GetMethodID( envptr, ClassIterator, "hasNext", "()Z" );
jboolean bHasNext = env->CallBooleanMethod( envptr, deviceListIterator, MethodhasNext );
ats+=sprintf(ats, "Has Devices: %d\n", bHasNext );
jmethodID Methodnext = env->GetMethodID( envptr, ClassIterator, "next", "()Ljava/lang/Object;" );
jclass ClassUsbDevice = env->FindClass( envptr, "android/hardware/usb/UsbDevice" );
jclass ClassUsbInterface = env->FindClass( envptr, "android/hardware/usb/UsbInterface" );
jclass ClassUsbEndpoint = env->FindClass( envptr, "android/hardware/usb/UsbEndpoint" );
jclass ClassUsbDeviceConnection = env->FindClass( envptr, "android/hardware/usb/UsbDeviceConnection" );
jmethodID MethodgetDeviceName = env->GetMethodID( envptr, ClassUsbDevice, "getDeviceName", "()Ljava/lang/String;" );
jmethodID MethodgetVendorId = env->GetMethodID( envptr, ClassUsbDevice, "getVendorId", "()I" );
jmethodID MethodgetProductId = env->GetMethodID( envptr, ClassUsbDevice, "getProductId", "()I" );
jmethodID MethodgetInterfaceCount = env->GetMethodID( envptr, ClassUsbDevice, "getInterfaceCount", "()I" );
jmethodID MethodgetInterface = env->GetMethodID( envptr, ClassUsbDevice, "getInterface", "(I)Landroid/hardware/usb/UsbInterface;" );
jmethodID MethodgetEndpointCount = env->GetMethodID( envptr, ClassUsbInterface, "getEndpointCount", "()I" );
jmethodID MethodgetEndpoint = env->GetMethodID( envptr, ClassUsbInterface, "getEndpoint", "(I)Landroid/hardware/usb/UsbEndpoint;" );
jmethodID MethodgetAddress = env->GetMethodID( envptr, ClassUsbEndpoint, "getAddress", "()I" );
jmethodID MethodgetMaxPacketSize = env->GetMethodID( envptr, ClassUsbEndpoint, "getMaxPacketSize", "()I" );
jobject matchingDevice = 0;
jobject matchingInterface = 0;
while( bHasNext )
{
// UsbDevice device = deviceIterator.next();
// Log.i(TAG,"Model: " + device.getDeviceName());
jobject device = env->CallObjectMethod( envptr, deviceListIterator, Methodnext );
uint16_t vendorId = env->CallIntMethod( envptr, device, MethodgetVendorId );
uint16_t productId = env->CallIntMethod( envptr, device, MethodgetProductId );
int ifaceCount = env->CallIntMethod( envptr, device, MethodgetInterfaceCount );
const char *strdevname = env->GetStringUTFChars(envptr, env->CallObjectMethod( envptr, device, MethodgetDeviceName ), 0);
ats+=sprintf(ats, "%s,%04x:%04x(%d)\n", strdevname,
vendorId,
productId, ifaceCount );
if( vendorId == vid && productId == pid )
{
if( ifaceCount )
{
matchingDevice = device;
matchingInterface = env->CallObjectMethod( envptr, device, MethodgetInterface, 0 );
}
}
bHasNext = env->CallBooleanMethod( envptr, deviceListIterator, MethodhasNext );
}
jobject matchingEp = 0;
if( matchingInterface )
{
//matchingInterface is of type android/hardware/usb/UsbInterface
int epCount = env->CallIntMethod( envptr, matchingInterface, MethodgetEndpointCount );
ats+=sprintf(ats, "Found device %d eps\n", epCount );
int i;
for( i = 0; i < epCount; i++ )
{
jobject endpoint = env->CallObjectMethod( envptr, matchingInterface, MethodgetEndpoint, i );
jint epnum = env->CallIntMethod( envptr, endpoint, MethodgetAddress );
jint mps = env->CallIntMethod( envptr, endpoint, MethodgetMaxPacketSize );
if( epnum == 0x02 ) matchingEp = endpoint;
ats+=sprintf(ats, "%p: %02x: MPS: %d (%c)\n", endpoint, epnum, mps, (matchingEp == endpoint)?'*':' ' );
}
}
jmethodID MethodopenDevice = env->GetMethodID( envptr, ClassUsbManager, "openDevice", "(Landroid/hardware/usb/UsbDevice;)Landroid/hardware/usb/UsbDeviceConnection;" );
jmethodID MethodrequestPermission = env->GetMethodID( envptr, ClassUsbManager, "requestPermission", "(Landroid/hardware/usb/UsbDevice;Landroid/app/PendingIntent;)V" );
jmethodID MethodhasPermission = env->GetMethodID( envptr, ClassUsbManager, "hasPermission", "(Landroid/hardware/usb/UsbDevice;)Z" );
jmethodID MethodclaimInterface = env->GetMethodID( envptr, ClassUsbDeviceConnection, "claimInterface", "(Landroid/hardware/usb/UsbInterface;Z)Z" );
jmethodID MethodsetInterface = env->GetMethodID( envptr, ClassUsbDeviceConnection, "setInterface", "(Landroid/hardware/usb/UsbInterface;)Z" );
jmethodID MethodgetFileDescriptor = env->GetMethodID( envptr, ClassUsbDeviceConnection, "getFileDescriptor", "()I" );
//jmethodID MethodbulkTransfer = env->GetMethodID( envptr, ClassUsbDeviceConnection, "bulkTransfer", "(Landroid/hardware/usb/UsbEndpoint;[BII)I" );
//see https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/hardware/usb/UsbDeviceConnection.java
//Calls: native_bulk_request -> android_hardware_UsbDeviceConnection_bulk_request -> usb_device_bulk_transfer
// UsbEndpoint endpoint, byte[] buffer, int length, int timeout
//bulkTransfer(UsbEndpoint endpoint, byte[] buffer, int length, int timeout)
//UsbDeviceConnection bulkTransfer
if( matchingEp && matchingDevice )
{
//UsbDeviceConnection deviceConnection = manager.openDevice( device )
deviceConnection = env->CallObjectMethod( envptr, manager, MethodopenDevice, matchingDevice );
jint epnum = env->CallIntMethod( envptr, matchingEp, MethodgetAddress );
if( !deviceConnection )
{
// hasPermission(UsbDevice device)
if( OGGetAbsoluteTime() - dTimeOfLastAsk < 5 )
{
ats+=sprintf(ats, "Asked for permission. Waiting to ask again." );
}
else if( env->CallBooleanMethod( envptr, manager, MethodhasPermission, matchingDevice ) )
{
ats+=sprintf(ats, "Has permission - disconnected?" );
}
else
{
//android.app.PendingIntent currently setting to 0 (null) seems not to cause crashes, but does force lock screen to happen.
//Because the screen locks we need to do a much more complicated operation, generating a PendingIntent. See Below.
// env->CallVoidMethod( envptr, manager, MethodrequestPermission, matchingDevice, 0 );
//This part mimiced off of:
//https://www.programcreek.com/java-api-examples/?class=android.hardware.usb.UsbManager&method=requestPermission
// manager.requestPermission(device, PendingIntent.getBroadcast(context, 0, new Intent(MainActivity.ACTION_USB_PERMISSION), 0));
jclass ClassPendingIntent = env->FindClass( envptr, "android/app/PendingIntent" );
jclass ClassIntent = env->FindClass(envptr, "android/content/Intent");
jmethodID newIntent = env->GetMethodID(envptr, ClassIntent, "<init>", "(Ljava/lang/String;)V");
jstring ACTION_USB_PERMISSION = env->NewStringUTF( envptr, "com.android.recipes.USB_PERMISSION" );
jobject intentObject = env->NewObject(envptr, ClassIntent, newIntent, ACTION_USB_PERMISSION);
jmethodID MethodgetBroadcast = env->GetStaticMethodID( envptr, ClassPendingIntent, "getBroadcast",
"(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;" );
jobject pi = env->CallStaticObjectMethod( envptr, ClassPendingIntent, MethodgetBroadcast, lNativeActivity, 0, intentObject, 0 );
//This actually requests permission.
env->CallVoidMethod( envptr, manager, MethodrequestPermission, matchingDevice, pi );
dTimeOfLastAsk = OGGetAbsoluteTime();
}
}
else
{
//Because we want to read and write to an interrupt endpoint, we need to claim the interface - it seems setting interfaces is insufficient here.
jboolean claimOk = env->CallBooleanMethod( envptr, deviceConnection, MethodclaimInterface, matchingInterface, 1 );
//jboolean claimOk = env->CallBooleanMethod( envptr, deviceConnection, MethodsetInterface, matchingInterface );
//jboolean claimOk = 1;
if( claimOk )
{
deviceConnectionFD = env->CallIntMethod( envptr, deviceConnection, MethodgetFileDescriptor );
}
ats+=sprintf(ats, "DC: %p; Claim: %d; FD: %d\n", deviceConnection, claimOk, deviceConnectionFD );
}
}
jnii->DetachCurrentThread( jniiptr );
return (!deviceConnectionFD)?-5:0;
}

View file

@ -0,0 +1,17 @@
//Copyright 2020 <>< Charles Lohr, You may use this file and library freely under the MIT/x11, NewBSD or ColorChord Licenses.
#ifndef _ANDROID_USB_DEVICES_H
#define _ANDROID_USB_DEVICES_H
#include <asset_manager.h>
#include <asset_manager_jni.h>
#include <android_native_app_glue.h>
int RequestPermissionOrGetConnectionFD( char * debug_status, uint16_t vid, uint16_t pid );
void DisconnectUSB(); //Disconnect from USB
extern jobject deviceConnection;
extern int deviceConnectionFD;
#endif

3
app/src/nativecode/cnfa/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
os_generic.h
example

View file

@ -0,0 +1,128 @@
//Copyright <>< 2010-2020 Charles Lohr (And other authors as cited)
//CNFA is licensed under the MIT/x11, ColorChord or NewBSD Licenses. You choose.
#ifndef _CNFA_C
#define _CNFA_C
#include "CNFA.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#if defined(WINDOWS) || defined(WIN32) || defined(WIN64) \
|| defined(_WIN32) || defined(_WIN64)
#ifndef strdup
#define strdup _strdup
#endif
#endif
static CNFAInitFn * CNFADrivers[MAX_CNFA_DRIVERS];
static char * CNFADriverNames[MAX_CNFA_DRIVERS];
static int CNFADriverPriorities[MAX_CNFA_DRIVERS];
void RegCNFADriver( int priority, const char * name, CNFAInitFn * fn )
{
int j;
if( priority <= 0 )
{
return;
}
printf("[CNFA] Registering Driver: %s\n", name);
for( j = MAX_CNFA_DRIVERS-1; j >= 0; j-- )
{
//Cruise along, find location to insert
if( j > 0 && ( !CNFADrivers[j-1] || CNFADriverPriorities[j-1] < priority ) )
{
CNFADrivers[j] = CNFADrivers[j-1];
CNFADriverNames[j] = CNFADriverNames[j-1];
CNFADriverPriorities[j] = CNFADriverPriorities[j-1];
}
else
{
CNFADrivers[j] = fn;
CNFADriverNames[j] = strdup( name );
CNFADriverPriorities[j] = priority;
break;
}
}
}
struct CNFADriver * CNFAInit( const char * driver_name, const char * your_name, CNFACBType cb, int reqSPSPlay, int reqSPSRec,
int reqChannelsPlay, int reqChannelsRec, int sugBufferSize, const char * outputSelect, const char * inputSelect, void * opaque)
{
#if defined( ANDROID ) || defined( __android__ )
//Android can't run static-time code.
void REGISTERAndroidCNFA();
REGISTERAndroidCNFA();
#endif
int i;
struct CNFADriver * ret = 0;
int minprio = 0;
CNFAInitFn * bestinit = 0;
if( driver_name == 0 || strlen( driver_name ) == 0 )
{
//Search for a driver.
for( i = 0; i < MAX_CNFA_DRIVERS; i++ )
{
if( CNFADrivers[i] == 0 )
{
break;
}
if( CNFADriverPriorities[i] > minprio )
{
minprio = CNFADriverPriorities[i];
bestinit = CNFADrivers[i];
}
}
if( bestinit )
{
ret = (struct CNFADriver *)bestinit( cb, your_name, reqSPSPlay, reqSPSRec, reqChannelsPlay, reqChannelsRec, sugBufferSize, outputSelect, inputSelect, opaque );
}
if( ret )
{
return ret;
}
}
else
{
for( i = 0; i < MAX_CNFA_DRIVERS; i++ )
{
if( CNFADrivers[i] == 0 )
{
break;
}
if( strcmp( CNFADriverNames[i], driver_name ) == 0 )
{
return (struct CNFADriver *)CNFADrivers[i]( cb, your_name, reqSPSPlay, reqSPSRec, reqChannelsPlay, reqChannelsRec, sugBufferSize, outputSelect, inputSelect, opaque );
}
}
}
printf( "CNFA Driver not found.\n" );
return 0;
}
int CNFAState( struct CNFADriver * cnfaobject )
{
if( cnfaobject )
{
return cnfaobject->StateFn( cnfaobject );
}
return -1;
}
void CNFAClose( struct CNFADriver * cnfaobject )
{
if( cnfaobject )
{
cnfaobject->CloseFn( cnfaobject );
}
}
#endif

View file

@ -0,0 +1,122 @@
//Copyright <>< 2010-2020 Charles Lohr (And other authors as cited)
//CNFA is licensed under the MIT/x11, ColorChord or NewBSD Licenses. You choose.
//
// CN's Platform-agnostic, foundational sound driver subsystem.
// Easily output and input sound on a variety of platforms.
//
// Options:
// * #define CNFA_IMPLEMENTATION before this header and it will build all
// definitions in.
//
#ifndef _CNFA_H
#define _CNFA_H
//this #define is per-platform. For instance on Linux, you have ALSA, Pulse and null
#define MAX_CNFA_DRIVERS 4
struct CNFADriver;
#ifdef __cplusplus
extern "C" {
#endif
#ifdef BUILD_DLL
#ifdef WINDOWS
#define DllExport __declspec( dllexport )
#else
#define DllExport extern
#endif
#else
#define DllExport
#endif
//NOTE: Some drivers have synchronous duplex mode, other drivers will use two different callbacks. If ether is unavailable, it will be NULL.
//I.e. if `out` is null, only use in to read. If in is null, only place samples in out.
typedef void(*CNFACBType)( struct CNFADriver * sd, short * out, short * in, int framesp, int framesr );
typedef void*(CNFAInitFn)( CNFACBType cb, const char * your_name, int reqSPSPlay, int reqSPSRec, int reqChannelsPlay, int reqChannelsRec, int sugBufferSize, const char * outputSelect, const char * inputSelect, void * opaque );
struct CNFADriver
{
void (*CloseFn)( void * object );
int (*StateFn)( void * object );
CNFACBType callback;
short channelsPlay;
short channelsRec;
int spsPlay;
int spsRec;
void * opaque;
//More fields may exist on a per-sound-driver basis
};
//Accepts:
//If DriverName = 0 or empty, will try to find best driver.
//
// our_source_name is an optional argument, but on some platforms controls the name of your endpoint.
// reqSPSPlay = 44100 is guaranteed on many platforms.
// reqSPSRec = 44100 is guaranteed on many platforms.
// NOTE: Some platforms do not allow SPS play and REC to deviate from each other.
// reqChannelsRec = 1 or 2 guaranteed on many platforms.
// reqChannelsPlay = 1 or 2 guaranteedon many platforms. NOTE: Some systems require ChannelsPlay == ChannelsRec!
// sugBufferSize = No promises.
// outputSelect = No standardization, NULL is OK for default.
// inputSelect = No standardization, NULL is OK for default.
DllExport struct CNFADriver * CNFAInit( const char * driver_name, const char * your_name, CNFACBType cb, int reqSPSPlay, int reqSPSRec, int reqChannelsPlay,
int reqChannelsRec, int sugBufferSize, const char * outputSelect, const char * inputSelect, void * opaque );
DllExport int CNFAState( struct CNFADriver * cnfaobject ); //returns bitmask. 1 if mic recording, 2 if play back running, 3 if both running.
DllExport void CNFAClose( struct CNFADriver * cnfaobject );
//Called by various sound drivers. Notice priority must be greater than 0. Priority of 0 or less will not register.
//This is an internal function. Applications shouldnot call it.
void RegCNFADriver( int priority, const char * name, CNFAInitFn * fn );
#if defined(_MSC_VER) && !defined(__clang__)
#define REGISTER_CNFA( cnfadriver, priority, name, function ) \
void REGISTER##cnfadriver() { RegCNFADriver( priority, name, function ); }
#else
#define REGISTER_CNFA( cnfadriver, priority, name, function ) \
void __attribute__((constructor)) REGISTER##cnfadriver() { RegCNFADriver( priority, name, function ); }
#endif
#ifdef __TINYC__
#ifndef TCC
#define TCC
#endif
#endif
#ifdef CNFA_IMPLEMENTATION
#include "CNFA.c"
#include "CNFA_null.c"
#if defined(WINDOWS) || defined(WIN32) || defined(WIN64)
#include "CNFA_winmm.c"
#include "CNFA_wasapi.c"
#elif defined( ANDROID ) || defined( __android__ )
#include "CNFA_android.c"
#elif defined(__NetBSD__) || defined(__sun)
#include "CNFA_sun.c"
#elif defined(__linux__)
#include "CNFA_alsa.c"
#if defined(PULSEAUDIO)
#include "CNFA_pulse.c"
#endif
#endif
#endif
#ifdef __cplusplus
};
#endif
#endif

View file

@ -0,0 +1,374 @@
//Copyright 2015-2020 <>< Charles Lohr under the MIT/x11, NewBSD or ColorChord License. You choose.
#include "CNFA.h"
#include "os_generic.h"
#include <alsa/asoundlib.h>
#include <string.h>
struct CNFADriverAlsa
{
void (*CloseFn)( void * object );
int (*StateFn)( void * object );
CNFACBType callback;
short channelsPlay;
short channelsRec;
int spsPlay;
int spsRec;
void * opaque;
char * devRec;
char * devPlay;
snd_pcm_uframes_t bufsize;
og_thread_t threadPlay;
og_thread_t threadRec;
snd_pcm_t *playback_handle;
snd_pcm_t *record_handle;
char playing;
char recording;
};
int CNFAStateAlsa( void * v )
{
struct CNFADriverAlsa * r = (struct CNFADriverAlsa *)v;
return ((r->playing)?2:0) | ((r->recording)?1:0);
}
void CloseCNFAAlsa( void * v )
{
struct CNFADriverAlsa * r = (struct CNFADriverAlsa *)v;
if( r )
{
if( r->playback_handle ) snd_pcm_close (r->playback_handle);
if( r->record_handle ) snd_pcm_close (r->record_handle);
if( r->threadPlay ) OGJoinThread( r->threadPlay );
if( r->threadRec ) OGJoinThread( r->threadRec );
OGUSleep(2000);
if( r->devRec ) free( r->devRec );
if( r->devPlay ) free( r->devPlay );
free( r );
}
}
static int SetHWParams( snd_pcm_t * handle, int * samplerate, short * channels, snd_pcm_uframes_t * bufsize, struct CNFADriverAlsa * a )
{
int err;
int bufs;
int dir;
snd_pcm_hw_params_t *hw_params;
if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) {
fprintf (stderr, "cannot allocate hardware parameter structure (%s)\n",
snd_strerror (err));
return -1;
}
if ((err = snd_pcm_hw_params_any (handle, hw_params)) < 0) {
fprintf (stderr, "cannot initialize hardware parameter structure (%s)\n",
snd_strerror (err));
goto fail;
}
if ((err = snd_pcm_hw_params_set_access (handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
fprintf (stderr, "cannot set access type (%s)\n",
snd_strerror (err));
goto fail;
}
if ((err = snd_pcm_hw_params_set_format (handle, hw_params, SND_PCM_FORMAT_S16_LE )) < 0) {
fprintf (stderr, "cannot set sample format (%s)\n",
snd_strerror (err));
goto fail;
}
if ((err = snd_pcm_hw_params_set_rate_near (handle, hw_params, (unsigned int*)samplerate, 0)) < 0) {
fprintf (stderr, "cannot set sample rate (%s)\n",
snd_strerror (err));
goto fail;
}
if ((err = snd_pcm_hw_params_set_channels (handle, hw_params, *channels)) < 0) {
fprintf (stderr, "cannot set channel count (%s)\n",
snd_strerror (err));
goto fail;
}
dir = 0;
if( (err = snd_pcm_hw_params_set_period_size_near(handle, hw_params, bufsize, &dir)) < 0 )
{
fprintf( stderr, "cannot set period size. (%s)\n",
snd_strerror(err) );
goto fail;
}
//NOTE: This step is critical for low-latency sound.
bufs = *bufsize*3;
if( (err = snd_pcm_hw_params_set_buffer_size(handle, hw_params, bufs)) < 0 )
{
fprintf( stderr, "cannot set snd_pcm_hw_params_set_buffer_size size. (%s)\n",
snd_strerror(err) );
goto fail;
}
if ((err = snd_pcm_hw_params (handle, hw_params)) < 0) {
fprintf (stderr, "cannot set parameters (%s)\n",
snd_strerror (err));
goto fail;
}
snd_pcm_hw_params_free (hw_params);
return 0;
fail:
snd_pcm_hw_params_free (hw_params);
return -2;
}
static int SetSWParams( struct CNFADriverAlsa * d, snd_pcm_t * handle, int isrec )
{
snd_pcm_sw_params_t *sw_params;
int err;
//Time for software parameters:
if( !isrec )
{
if ((err = snd_pcm_sw_params_malloc (&sw_params)) < 0) {
fprintf (stderr, "cannot allocate software parameters structure (%s)\n",
snd_strerror (err));
goto failhard;
}
if ((err = snd_pcm_sw_params_current (handle, sw_params)) < 0) {
fprintf (stderr, "cannot initialize software parameters structure (%s) (%p)\n",
snd_strerror (err), handle);
goto fail;
}
int buffer_size = d->bufsize*3;
int period_size = d->bufsize;
printf( "PERIOD: %d BUFFER: %d\n", period_size, buffer_size );
if ((err = snd_pcm_sw_params_set_avail_min (handle, sw_params, period_size )) < 0) {
fprintf (stderr, "cannot set minimum available count (%s)\n",
snd_strerror (err));
goto fail;
}
//if ((err = snd_pcm_sw_params_set_stop_threshold(handle, sw_params, 512 )) < 0) {
// fprintf (stderr, "cannot set minimum available count (%s)\n",
// snd_strerror (err));
// goto fail;
//}
if ((err = snd_pcm_sw_params_set_start_threshold(handle, sw_params, buffer_size - period_size )) < 0) {
fprintf (stderr, "cannot set minimum available count (%s)\n",
snd_strerror (err));
goto fail;
}
if ((err = snd_pcm_sw_params (handle, sw_params)) < 0) {
fprintf (stderr, "cannot set software parameters (%s)\n",
snd_strerror (err));
goto fail;
}
}
if ((err = snd_pcm_prepare (handle)) < 0) {
fprintf (stderr, "cannot prepare audio interface for use (%s)\n",
snd_strerror (err));
goto fail;
}
return 0;
fail:
if( !isrec )
{
snd_pcm_sw_params_free (sw_params);
}
failhard:
return -1;
}
void * RecThread( void * v )
{
struct CNFADriverAlsa * r = (struct CNFADriverAlsa *)v;
short samples[r->bufsize * r->channelsRec];
snd_pcm_start(r->record_handle);
do
{
int err = snd_pcm_readi( r->record_handle, samples, r->bufsize );
if( err < 0 )
{
fprintf( stderr, "Warning: ALSA Recording Failed\n" );
break;
}
if( err != r->bufsize )
{
fprintf( stderr, "Warning: ALSA Recording Underflow\n" );
}
r->recording = 1;
r->callback( (struct CNFADriver *)r, 0, samples, 0, err );
} while( 1 );
r->recording = 0;
fprintf( stderr, "ALSA Recording Stopped\n" );
return 0;
}
void * PlayThread( void * v )
{
struct CNFADriverAlsa * r = (struct CNFADriverAlsa *)v;
short samples[r->bufsize * r->channelsPlay];
int err;
//int total_avail = snd_pcm_avail(r->playback_handle);
snd_pcm_start(r->playback_handle);
r->callback( (struct CNFADriver *)r, samples, 0, r->bufsize, 0 );
err = snd_pcm_writei(r->playback_handle, samples, r->bufsize);
while( err >= 0 )
{
// int avail = snd_pcm_avail(r->playback_handle);
// printf( "avail: %d\n", avail );
r->callback( (struct CNFADriver *)r, samples, 0, r->bufsize, 0 );
err = snd_pcm_writei(r->playback_handle, samples, r->bufsize);
if( err != r->bufsize )
{
fprintf( stderr, "Warning: ALSA Playback Overflow\n" );
}
r->playing = 1;
}
r->playing = 0;
fprintf( stderr, "ALSA Playback Stopped\n" );
return 0;
}
static struct CNFADriverAlsa * InitALSA( struct CNFADriverAlsa * r )
{
printf( "CNFA Alsa Init %p %p (%d %d) %d %d\n", r->playback_handle, r->record_handle, r->spsPlay, r->spsRec, r->channelsPlay, r->channelsRec );
int err;
if( r->channelsPlay )
{
if ((err = snd_pcm_open (&r->playback_handle, r->devPlay?r->devPlay:"default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
fprintf (stderr, "cannot open output audio device (%s)\n",
snd_strerror (err));
goto fail;
}
}
if( r->channelsRec )
{
if ((err = snd_pcm_open (&r->record_handle, r->devRec?r->devRec:"default", SND_PCM_STREAM_CAPTURE, 0)) < 0) {
fprintf (stderr, "cannot open input audio device (%s)\n",
snd_strerror (err));
goto fail;
}
}
printf( "%p %p\n", r->playback_handle, r->record_handle );
if( r->playback_handle )
{
if( SetHWParams( r->playback_handle, &r->spsPlay, &r->channelsPlay, &r->bufsize, r ) < 0 )
goto fail;
if( SetSWParams( r, r->playback_handle, 0 ) < 0 )
goto fail;
}
if( r->record_handle )
{
if( SetHWParams( r->record_handle, &r->spsRec, &r->channelsRec, &r->bufsize, r ) < 0 )
goto fail;
if( SetSWParams( r, r->record_handle, 1 ) < 0 )
goto fail;
}
#if 0
if( r->playback_handle )
{
snd_async_handler_t *pcm_callback;
//Handle automatically cleaned up when stream closed.
err = snd_async_add_pcm_handler(&pcm_callback, r->playback_handle, playback_callback, r);
if(err < 0)
{
printf("Playback callback handler error: %s\n", snd_strerror(err));
}
}
if( r->record_handle )
{
snd_async_handler_t *pcm_callback;
//Handle automatically cleaned up when stream closed.
err = snd_async_add_pcm_handler(&pcm_callback, r->record_handle, record_callback, r);
if(err < 0)
{
printf("Record callback handler error: %s\n", snd_strerror(err));
}
}
#endif
if( r->playback_handle && r->record_handle )
{
err = snd_pcm_link ( r->playback_handle, r->record_handle );
if(err < 0)
{
printf("snd_pcm_link error: %s\n", snd_strerror(err));
}
}
if( r->playback_handle )
{
r->threadPlay = OGCreateThread( PlayThread, r );
}
if( r->record_handle )
{
r->threadRec = OGCreateThread( RecThread, r );
}
printf( "CNFA Alsa Init Out -> %p %p (%d %d) %d %d\n", r->playback_handle, r->record_handle, r->spsPlay, r->spsRec, r->channelsPlay, r->channelsRec );
return r;
fail:
if( r )
{
if( r->playback_handle ) snd_pcm_close (r->playback_handle);
if( r->record_handle ) snd_pcm_close (r->record_handle);
free( r );
}
fprintf( stderr, "Error: ALSA failed to start.\n" );
return 0;
}
void * InitALSADriver( CNFACBType cb, const char * your_name, int reqSPSPlay, int reqSPSRec, int reqChannelsPlay, int reqChannelsRec, int sugBufferSize, const char * outputSelect, const char * inputSelect, void * opaque )
{
struct CNFADriverAlsa * r = (struct CNFADriverAlsa *)malloc( sizeof( struct CNFADriverAlsa ) );
r->CloseFn = CloseCNFAAlsa;
r->StateFn = CNFAStateAlsa;
r->callback = cb;
r->opaque = opaque;
r->spsPlay = reqSPSPlay;
r->spsRec = reqSPSRec;
r->channelsPlay = reqChannelsPlay;
r->channelsRec = reqChannelsRec;
r->devRec = (inputSelect)?strdup(inputSelect):0;
r->devPlay = (outputSelect)?strdup(outputSelect):0;
r->playback_handle = 0;
r->record_handle = 0;
r->bufsize = sugBufferSize;
return InitALSA(r);
}
REGISTER_CNFA( ALSA, 10, "ALSA", InitALSADriver );

View file

@ -0,0 +1,319 @@
//Copyright 2019-2020 <>< Charles Lohr under the ColorChord License, MIT/x11 license or NewBSD Licenses.
// This was originally to be used with rawdrawandroid
#include "CNFA.h"
#include "os_generic.h"
#include <pthread.h> //Using android threads not os_generic threads.
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//based on https://github.com/android/ndk-samples/blob/master/native-audio/app/src/main/cpp/native-audio-jni.c
// for native audio
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include <android_native_app_glue.h>
#include <jni.h>
#include <native_activity.h>
struct CNFADriverAndroid
{
//Standard header - must remain.
void (*CloseFn)( void * object );
int (*StateFn)( void * object );
CNFACBType callback;
short channelsPlay;
short channelsRec;
int spsPlay;
int spsRec;
void * opaque;
int buffsz;
SLObjectItf engineObject;
SLEngineItf engineEngine;
SLRecordItf recorderRecord;
SLObjectItf recorderObject;
SLPlayItf playerPlay;
SLObjectItf playerObject;
SLObjectItf outputMixObject;
SLAndroidSimpleBufferQueueItf recorderBufferQueue;
SLAndroidSimpleBufferQueueItf playerBufferQueue;
//unsigned recorderSize;
int recorderBufferSizeBytes;
int playerBufferSizeBytes;
short * recorderBuffer;
short * playerBuffer;
};
void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
{
struct CNFADriverAndroid * r = (struct CNFADriverAndroid*)context;
r->callback( (struct CNFADriver*)r, 0, r->recorderBuffer, 0, r->buffsz/(sizeof(short)*r->channelsRec) );
(*r->recorderBufferQueue)->Enqueue( r->recorderBufferQueue, r->recorderBuffer, r->recorderBufferSizeBytes/(r->channelsRec*sizeof(short)) );
}
void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
{
struct CNFADriverAndroid * r = (struct CNFADriverAndroid*)context;
r->callback( (struct CNFADriver*)r, r->playerBuffer, 0, r->buffsz/(sizeof(short)*r->channelsPlay), 0 );
(*r->playerBufferQueue)->Enqueue( r->playerBufferQueue, r->playerBuffer, r->playerBufferSizeBytes/(r->channelsPlay*sizeof(short)));
}
static struct CNFADriverAndroid* InitAndroidDriver( struct CNFADriverAndroid * r )
{
SLresult result;
printf( "Starting InitAndroidDriver\n" );
// create engine
result = slCreateEngine(&r->engineObject, 0, NULL, 0, NULL, NULL);
assert(SL_RESULT_SUCCESS == result);
(void)result;
// realize the engine
result = (*r->engineObject)->Realize(r->engineObject, SL_BOOLEAN_FALSE);
assert(SL_RESULT_SUCCESS == result);
(void)result;
// get the engine interface, which is needed in order to create other objects
result = (*r->engineObject)->GetInterface(r->engineObject, SL_IID_ENGINE, &r->engineEngine);
assert(SL_RESULT_SUCCESS == result);
(void)result;
///////////////////////////////////////////////////////////////////////////////////////////////////////
if( r->channelsPlay )
{
printf("create output mix");
SLDataFormat_PCM format_pcm ={
SL_DATAFORMAT_PCM,
r->channelsPlay,
r->spsPlay*1000,
SL_PCMSAMPLEFORMAT_FIXED_16,
SL_PCMSAMPLEFORMAT_FIXED_16,
(r->channelsPlay==1)?SL_SPEAKER_FRONT_CENTER:3,
SL_BYTEORDER_LITTLEENDIAN,
};
SLDataLocator_AndroidSimpleBufferQueue loc_bq_play = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
SLDataSource source = {&loc_bq_play, &format_pcm};
const SLInterfaceID ids[1] = {SL_IID_VOLUME};
const SLboolean req[1] = {SL_BOOLEAN_TRUE};
const SLInterfaceID id[1] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE};
result = (*r->engineEngine)->CreateOutputMix(r->engineEngine, &r->outputMixObject, 0, ids, req);
result = (*r->outputMixObject)->Realize(r->outputMixObject, SL_BOOLEAN_FALSE);
SLDataLocator_OutputMix loc_outmix = { SL_DATALOCATOR_OUTPUTMIX, r->outputMixObject };
SLDataSink sink;
sink.pFormat = &format_pcm;
sink.pLocator = &loc_outmix;
// create audio player
result = (*r->engineEngine)->CreateAudioPlayer(r->engineEngine, &r->playerObject, &source, &sink, 1, id, req);
if (SL_RESULT_SUCCESS != result) {
printf( "CreateAudioPlayer failed\n" );
return JNI_FALSE;
}
// realize the audio player
result = (*r->playerObject)->Realize(r->playerObject, SL_BOOLEAN_FALSE);
if (SL_RESULT_SUCCESS != result) {
printf( "AudioPlayer Realize failed: %d\n", result );
return JNI_FALSE;
}
// get the player interface
result = (*r->playerObject)->GetInterface(r->playerObject, SL_IID_PLAY, &r->playerPlay);
assert(SL_RESULT_SUCCESS == result);
(void)result;
// get the buffer queue interface
result = (*r->playerObject)->GetInterface(r->playerObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &r->playerBufferQueue);
assert(SL_RESULT_SUCCESS == result);
(void)result;
// register callback on the buffer queue
result = (*r->playerBufferQueue)->RegisterCallback(r->playerBufferQueue, bqPlayerCallback, r);
assert(SL_RESULT_SUCCESS == result);
(void)result;
printf( "===================== Player init ok.\n" );
}
if( r->channelsRec )
{
// configure audio source
SLDataLocator_IODevice loc_devI = {SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, SL_DEFAULTDEVICEID_AUDIOINPUT, NULL};
SLDataSource audioSrc = {&loc_devI, NULL};
// configure audio sink
SLDataFormat_PCM format_pcm ={
SL_DATAFORMAT_PCM,
r->channelsRec,
r->spsRec*1000,
SL_PCMSAMPLEFORMAT_FIXED_16,
SL_PCMSAMPLEFORMAT_FIXED_16,
(r->channelsRec==1)?SL_SPEAKER_FRONT_CENTER:3,
SL_BYTEORDER_LITTLEENDIAN,
};
SLDataLocator_AndroidSimpleBufferQueue loc_bq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
SLDataSink audioSnk = {&loc_bq, &format_pcm};
const SLInterfaceID id[1] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE};
const SLboolean req[1] = {SL_BOOLEAN_TRUE};
result = (*r->engineEngine)->CreateAudioRecorder(r->engineEngine, &r->recorderObject, &audioSrc, &audioSnk, 1, id, req);
if (SL_RESULT_SUCCESS != result) {
printf( "CreateAudioRecorder failed\n" );
return JNI_FALSE;
}
// realize the audio recorder
result = (*r->recorderObject)->Realize(r->recorderObject, SL_BOOLEAN_FALSE);
if (SL_RESULT_SUCCESS != result) {
printf( "AudioRecorder Realize failed: %d\n", result );
return JNI_FALSE;
}
// get the record interface
result = (*r->recorderObject)->GetInterface(r->recorderObject, SL_IID_RECORD, &r->recorderRecord);
assert(SL_RESULT_SUCCESS == result);
(void)result;
// get the buffer queue interface
result = (*r->recorderObject)->GetInterface(r->recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &r->recorderBufferQueue);
assert(SL_RESULT_SUCCESS == result);
(void)result;
// register callback on the buffer queue
result = (*r->recorderBufferQueue)->RegisterCallback(r->recorderBufferQueue, bqRecorderCallback, r);
assert(SL_RESULT_SUCCESS == result);
(void)result;
}
if( r->playerPlay )
{
result = (*r->playerPlay)->SetPlayState(r->playerPlay, SL_PLAYSTATE_STOPPED);
assert(SL_RESULT_SUCCESS == result); (void)result;
result = (*r->playerBufferQueue)->Clear(r->playerBufferQueue);
assert(SL_RESULT_SUCCESS == result); (void)result;
r->playerBuffer = malloc( r->playerBufferSizeBytes );
memset( r->playerBuffer, 0, r->playerBufferSizeBytes );
result = (*r->playerBufferQueue)->Enqueue(r->playerBufferQueue, r->playerBuffer, r->playerBufferSizeBytes );
assert(SL_RESULT_SUCCESS == result); (void)result;
result = (*r->playerPlay)->SetPlayState(r->playerPlay, SL_PLAYSTATE_PLAYING);
assert(SL_RESULT_SUCCESS == result); (void)result;
}
if( r->recorderRecord )
{
result = (*r->recorderRecord)->SetRecordState(r->recorderRecord, SL_RECORDSTATE_STOPPED);
assert(SL_RESULT_SUCCESS == result); (void)result;
result = (*r->recorderBufferQueue)->Clear(r->recorderBufferQueue);
assert(SL_RESULT_SUCCESS == result); (void)result;
// the buffer is not valid for playback yet
r->recorderBuffer = malloc( r->recorderBufferSizeBytes );
// enqueue an empty buffer to be filled by the recorder
// (for streaming recording, we would enqueue at least 2 empty buffers to start things off)
result = (*r->recorderBufferQueue)->Enqueue(r->recorderBufferQueue, r->recorderBuffer, r->recorderBufferSizeBytes );
// the most likely other result is SL_RESULT_BUFFER_INSUFFICIENT,
// which for this code example would indicate a programming error
assert(SL_RESULT_SUCCESS == result); (void)result;
// start recording
result = (*r->recorderRecord)->SetRecordState(r->recorderRecord, SL_RECORDSTATE_RECORDING);
assert(SL_RESULT_SUCCESS == result); (void)result;
}
printf( "Complete Init Sound Android\n" );
return r;
}
int CNFAStateAndroid( void * v )
{
struct CNFADriverAndroid * soundobject = (struct CNFADriverAndroid *)v;
return ((soundobject->recorderObject)?1:0) | ((soundobject->playerObject)?2:0);
}
void CloseCNFAAndroid( void * v )
{
struct CNFADriverAndroid * r = (struct CNFADriverAndroid *)v;
// destroy audio recorder object, and invalidate all associated interfaces
if (r->recorderObject != NULL) {
(*r->recorderObject)->Destroy(r->recorderObject);
r->recorderObject = NULL;
r->recorderRecord = NULL;
r->recorderBufferQueue = NULL;
if( r->recorderBuffer ) free( r->recorderBuffer );
}
if (r->playerObject != NULL) {
(*r->playerObject)->Destroy(r->playerObject);
r->playerObject = NULL;
r->playerPlay = NULL;
r->playerBufferQueue = NULL;
if( r->playerBuffer ) free( r->playerBuffer );
}
// destroy engine object, and invalidate all associated interfaces
if (r->engineObject != NULL) {
(*r->engineObject)->Destroy(r->engineObject);
r->engineObject = NULL;
r->engineEngine = NULL;
}
}
int AndroidHasPermissions(const char* perm_name);
void AndroidRequestAppPermissions(const char * perm);
void * InitCNFAAndroid( CNFACBType cb, const char * your_name, int reqSPSPlay, int reqSPSRec, int reqChannelsPlay, int reqChannelsRec, int sugBufferSize, const char * outputSelect, const char * inputSelect, void * opaque )
{
struct CNFADriverAndroid * r = (struct CNFADriverAndroid *)malloc( sizeof( struct CNFADriverAndroid ) );
memset( r, 0, sizeof( *r) );
r->CloseFn = CloseCNFAAndroid;
r->StateFn = CNFAStateAndroid;
r->callback = cb;
r->opaque = opaque;
r->channelsPlay = reqChannelsPlay;
r->channelsRec = reqChannelsRec;
r->spsRec = reqSPSRec;
r->spsPlay = reqSPSPlay;
r->recorderBufferSizeBytes = sugBufferSize * 2 * r->channelsRec;
r->playerBufferSizeBytes = sugBufferSize * 2 * r->channelsPlay;
int hasperm = AndroidHasPermissions( "RECORD_AUDIO" );
if( !hasperm )
{
AndroidRequestAppPermissions( "RECORD_AUDIO" );
}
r->buffsz = sugBufferSize;
return InitAndroidDriver(r);
}
//Tricky: On Android, this can't actually run before main. Have to manually execute it.
REGISTER_CNFA( AndroidCNFA, 10, "ANDROID", InitCNFAAndroid );

View file

@ -0,0 +1,46 @@
//Copyright 2015-2020 <>< Charles Lohr under the ColorChord License.
#include "CNFA.h"
#include "os_generic.h"
#include <stdlib.h>
struct CNFADriverNull
{
void (*CloseFn)( void * object );
int (*StateFn)( void * object );
CNFACBType callback;
short channelsPlay;
short channelsRec;
int spsPlay;
int spsRec;
void * opaque;
};
void CloseCNFANull( void * object )
{
free( object );
}
int CNFAStateNull( void * object )
{
return 0;
}
void * InitCNFANull( CNFACBType cb, const char * your_name, int reqSPSPlay, int reqSPSRec, int reqChannelsPlay, int reqChannelsRec, int sugBufferSize, const char * outputSelect, const char * inputSelect, void * opaque )
{
struct CNFADriverNull * r = (struct CNFADriverNull *)malloc( sizeof( struct CNFADriverNull ) );
r->CloseFn = CloseCNFANull;
r->StateFn = CNFAStateNull;
r->callback = cb;
r->spsPlay = reqSPSPlay;
r->spsRec = reqSPSRec;
r->opaque = opaque;
r->channelsPlay = reqChannelsPlay;
r->channelsRec = reqChannelsRec;
return r;
}
REGISTER_CNFA( NullCNFA, 1, "NULL", InitCNFANull );

View file

@ -0,0 +1,308 @@
//Copyright 2015-2020 <>< Charles Lohr under the MIT/x11, NewBSD or ColorChord License. You choose.
//This file is really rough. Full duplex doesn't seem to work hardly at all.
#include "CNFA.h"
#include "os_generic.h"
#include <stdlib.h>
#include <pulse/simple.h>
#include <pulse/pulseaudio.h>
#include <pulse/error.h>
#include <stdio.h>
#include <string.h>
#define BUFFERSETS 3
//from http://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/Developer/Clients/Samples/AsyncPlayback/
//also http://maemo.org/api_refs/5.0/5.0-final/pulseaudio/pacat_8c-example.html
struct CNFADriverPulse
{
void (*CloseFn)( void * object );
int (*StateFn)( void * object );
CNFACBType callback;
short channelsPlay;
short channelsRec;
int spsPlay;
int spsRec;
void * opaque;
char * sourceNamePlay;
char * sourceNameRec;
og_thread_t thread;
pa_stream * play;
pa_stream * rec;
pa_context * pa_ctx;
pa_mainloop *pa_ml;
int pa_ready;
int buffer;
//More fields may exist on a per-sound-driver basis
};
int CNFAStatePulse( void * v )
{
struct CNFADriverPulse * soundobject = (struct CNFADriverPulse *)v;
return ((soundobject->play)?2:0) | ((soundobject->rec)?1:0);
}
void CloseCNFAPulse( void * v )
{
struct CNFADriverPulse * r = (struct CNFADriverPulse *)v;
if( r )
{
if( r->play )
{
pa_stream_unref (r->play);
r->play = 0;
}
if( r->rec )
{
pa_stream_unref (r->rec);
r->rec = 0;
}
OGUSleep(2000);
OGCancelThread( r->thread );
if( r->sourceNamePlay ) free( r->sourceNamePlay );
if( r->sourceNameRec ) free( r->sourceNameRec );
free( r );
}
}
static void * CNFAThread( void * v )
{
struct CNFADriverPulse * r = (struct CNFADriverPulse*)v;
while(1)
{
pa_mainloop_iterate( r->pa_ml, 1, NULL );
}
return 0;
}
static void stream_request_cb(pa_stream *s, size_t length, void *userdata)
{
struct CNFADriverPulse * r = (struct CNFADriverPulse*)userdata;
if( !r->play )
{
return;
}
short bufp[length*r->channelsPlay/sizeof(short)];
r->callback( (struct CNFADriver*)r, bufp, 0, length/(sizeof(short)*r->channelsPlay), 0 );
pa_stream_write(r->play, &bufp[0], length, NULL, 0LL, PA_SEEK_RELATIVE);
}
static void stream_record_cb(pa_stream *s, size_t length, void *userdata)
{
struct CNFADriverPulse * r = (struct CNFADriverPulse*)userdata;
uint16_t * bufr;
if (pa_stream_peek(r->rec, (const void**)&bufr, &length) < 0) {
fprintf(stderr, ("pa_stream_peek() failed: %s\n"), pa_strerror(pa_context_errno(r->pa_ctx)));
return;
}
short * buffer;
buffer = (short*)pa_xmalloc(length);
memcpy(buffer, bufr, length);
pa_stream_drop(r->rec);
r->callback( (struct CNFADriver*)r, 0, buffer, 0, length/(sizeof(short)*r->channelsRec) );
pa_xfree( buffer );
}
static void stream_underflow_cb(pa_stream *s, void *userdata) {
printf("underflow\n");
}
void pa_state_cb(pa_context *c, void *userdata) {
pa_context_state_t state;
int *pa_ready = (int*)userdata;
state = pa_context_get_state(c);
switch (state) {
// These are just here for reference
case PA_CONTEXT_UNCONNECTED:
case PA_CONTEXT_CONNECTING:
case PA_CONTEXT_AUTHORIZING:
case PA_CONTEXT_SETTING_NAME:
default:
break;
case PA_CONTEXT_FAILED:
case PA_CONTEXT_TERMINATED:
*pa_ready = 2;
break;
case PA_CONTEXT_READY:
*pa_ready = 1;
break;
}
}
void * InitCNFAPulse( CNFACBType cb, const char * your_name, int reqSPSPlay, int reqSPSRec, int reqChannelsPlay, int reqChannelsRec, int sugBufferSize, const char * outputSelect, const char * inputSelect, void * opaque )
{
static pa_buffer_attr bufattr;
static pa_sample_spec ss;
int error;
pa_mainloop_api *pa_mlapi;
const char * title = your_name;
struct CNFADriverPulse * r = (struct CNFADriverPulse *)malloc( sizeof( struct CNFADriverPulse ) );
r->pa_ml = pa_mainloop_new();
if( !r->pa_ml )
{
fprintf( stderr, "Failed to initialize pa_mainloop_new()\n" );
goto fail;
}
pa_mlapi = pa_mainloop_get_api(r->pa_ml);
if( !pa_mlapi )
{
fprintf( stderr, "Failed to initialize pa_mainloop_get_api()\n" );
goto fail;
}
r->pa_ctx = pa_context_new(pa_mlapi, title );
pa_context_connect(r->pa_ctx, NULL, PA_CONTEXT_NOFLAGS, NULL);
//TODO: pa_context_set_state_callback
r->CloseFn = CloseCNFAPulse;
r->StateFn = CNFAStatePulse;
r->callback = cb;
r->opaque = opaque;
r->spsPlay = reqSPSPlay;
r->spsRec = reqSPSRec;
r->channelsPlay = reqChannelsPlay;
r->channelsRec = reqChannelsRec;
r->sourceNamePlay = outputSelect?strdup(outputSelect):0;
r->sourceNameRec = inputSelect?strdup(inputSelect):0;
r->play = 0;
r->rec = 0;
r->buffer = sugBufferSize;
printf ("Pulse: from: [O/I] %s/%s (%s) / (%d,%d)x(%d,%d) (%d)\n", r->sourceNamePlay, r->sourceNameRec, title, r->spsPlay, r->spsRec, r->channelsPlay, r->channelsRec, r->buffer );
memset( &ss, 0, sizeof( ss ) );
ss.format = PA_SAMPLE_S16NE;
r->pa_ready = 0;
pa_context_set_state_callback(r->pa_ctx, pa_state_cb, &r->pa_ready);
while (r->pa_ready == 0)
{
pa_mainloop_iterate(r->pa_ml, 1, NULL);
}
int bufbytes = r->buffer * sizeof(short) * r->channelsRec;
if( r->channelsPlay )
{
ss.channels = r->channelsPlay;
ss.rate = r->spsPlay;
if (!(r->play = pa_stream_new(r->pa_ctx, "Play", &ss, NULL))) {
error = -3; //XXX ??? TODO
fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error));
goto fail;
}
pa_stream_set_underflow_callback(r->play, stream_underflow_cb, NULL);
pa_stream_set_write_callback(r->play, stream_request_cb, r );
bufattr.fragsize = (uint32_t)-1;
bufattr.maxlength = bufbytes*3; //XXX TODO Consider making this -1
bufattr.minreq = 0;
bufattr.prebuf = (uint32_t)-1;
bufattr.tlength = bufbytes*3;
int ret = pa_stream_connect_playback(r->play, r->sourceNamePlay, &bufattr,
// PA_STREAM_INTERPOLATE_TIMING
// |PA_STREAM_ADJUST_LATENCY //Some servers don't like the adjust_latency flag.
// |PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL);
PA_STREAM_NOFLAGS, NULL, NULL );
if( ret < 0 )
{
fprintf(stderr, __FILE__": (PLAY) pa_stream_connect_playback() failed: %s\n", pa_strerror(ret));
goto fail;
}
}
if( r->channelsRec )
{
ss.channels = r->channelsRec;
ss.rate = r->spsRec;
if (!(r->rec = pa_stream_new(r->pa_ctx, "Record", &ss, NULL))) {
error = -3; //XXX ??? TODO
fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error));
goto fail;
}
pa_stream_set_read_callback(r->rec, stream_record_cb, r );
bufattr.fragsize = bufbytes;
bufattr.maxlength = (uint32_t)-1;//(uint32_t)-1; //XXX: Todo, should this be low?
bufattr.minreq = bufbytes;
bufattr.prebuf = (uint32_t)-1;
bufattr.tlength = bufbytes*3;
int ret = pa_stream_connect_record(r->rec, r->sourceNameRec, &bufattr,
// PA_STREAM_INTERPOLATE_TIMING
PA_STREAM_ADJUST_LATENCY //Some servers don't like the adjust_latency flag.
// PA_STREAM_AUTO_TIMING_UPDATE
// PA_STREAM_NOFLAGS
);
printf( "PA REC RES: %d\n", ret );
if( ret < 0 )
{
fprintf(stderr, __FILE__": (REC) pa_stream_connect_playback() failed: %s\n", pa_strerror(ret));
goto fail;
}
}
printf( "Pulse initialized.\n" );
r->thread = OGCreateThread( CNFAThread, r );
if( r->play )
{
stream_request_cb( r->play, bufbytes, r );
stream_request_cb( r->play, bufbytes, r );
}
return r;
fail:
if( r )
{
if( r->play ) pa_xfree (r->play);
if( r->rec ) pa_xfree (r->rec);
free( r );
}
return 0;
}
REGISTER_CNFA( PulseCNFA, 11, "PULSE", InitCNFAPulse );

View file

@ -0,0 +1,275 @@
//Copyright 2015-2020 <>< Charles Lohr under the MIT/x11, NewBSD or ColorChord License. You choose.
#include "CNFA.h"
#include "os_generic.h"
#include <sys/audioio.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
struct CNFADriverSun
{
void (*CloseFn)( void * object );
int (*StateFn)( void * object );
CNFACBType callback;
short channelsPlay;
short channelsRec;
int spsPlay;
int spsRec;
void * opaque;
char * devRec;
char * devPlay;
short * samplesRec;
short * samplesPlay;
og_thread_t threadPlay;
og_thread_t threadRec;
int bufsize;
int playback_handle;
int record_handle;
char playing;
char recording;
};
int CNFAStateSun( void * v )
{
struct CNFADriverSun * r = (struct CNFADriverSun *)v;
return ((r->playing)?2:0) | ((r->recording)?1:0);
}
void CloseCNFASun( void * v )
{
struct CNFADriverSun * r = (struct CNFADriverSun *)v;
if( r )
{
if( r->playback_handle != -1 ) close (r->playback_handle);
if( r->record_handle != -1 ) close (r->record_handle);
if( r->threadPlay ) OGJoinThread( r->threadPlay );
if( r->threadRec ) OGJoinThread( r->threadRec );
OGUSleep(2000);
free( r->devRec );
free( r->devPlay );
free( r->samplesRec );
free( r->samplesPlay );
free( r );
}
}
void * RecThread( void * v )
{
struct CNFADriverSun * r = (struct CNFADriverSun *)v;
size_t nbytes = r->bufsize * (2 * r->channelsRec);
do
{
int nread = read( r->record_handle, r->samplesRec, nbytes );
if( nread < 0 )
{
fprintf( stderr, "Warning: Sun Recording Failed\n" );
break;
}
r->recording = 1;
r->callback( (struct CNFADriver *)r, NULL, r->samplesRec, 0, (nread / 2) / r->channelsRec);
} while( 1 );
r->recording = 0;
fprintf( stderr, "Sun Recording Stopped\n" );
return 0;
}
void * PlayThread( void * v )
{
struct CNFADriverSun * r = (struct CNFADriverSun *)v;
size_t nbytes = r->bufsize * (2 * r->channelsPlay);
int err;
r->callback( (struct CNFADriver *)r, r->samplesPlay, NULL, r->bufsize, 0 );
err = write( r->playback_handle, r->samplesPlay, nbytes );
while( err >= 0 )
{
r->callback( (struct CNFADriver *)r, r->samplesPlay, NULL, r->bufsize, 0 );
err = write( r->playback_handle, r->samplesPlay, nbytes );
r->playing = 1;
}
r->playing = 0;
fprintf( stderr, "Sun Playback Stopped\n" );
return 0;
}
static struct CNFADriverSun * InitSun( struct CNFADriverSun * r )
{
const char * devPlay = r->devPlay;
const char * devRec = r->devRec;
struct audio_info rinfo, pinfo;
if( devRec == NULL || strcmp ( devRec, "default" ) == 0 )
{
devRec = "/dev/audio";
}
if( devPlay == NULL || strcmp ( devPlay , "default" ) == 0 )
{
devPlay = "/dev/audio";
}
printf( "CNFA Sun Init -> devPlay: %s, channelsPlay: %d, spsPlay: %d, devRec: %s, channelsRec: %d, spsRec: %d\n", devPlay, r->channelsPlay, r->spsPlay, devRec, r->channelsRec, r->spsRec);
if( r->channelsPlay && r->channelsRec && strcmp (devPlay, devRec) == 0 )
{
if ( (r->playback_handle = r->record_handle = open (devPlay, O_RDWR)) < 0 )
{
fprintf (stderr, "cannot open audio device (%s)\n",
strerror (errno));
goto fail;
}
}
else
{
if( r->channelsPlay )
{
if ( (r->playback_handle = open (devPlay, O_WRONLY)) < 0 )
{
fprintf (stderr, "cannot open output audio device %s (%s)\n",
r->devPlay, strerror (errno));
goto fail;
}
}
if( r->channelsRec )
{
if ( (r->record_handle = open (devRec, O_RDONLY)) < 0 )
{
fprintf (stderr, "cannot open input audio device %s (%s)\n",
r->devRec, strerror (errno));
goto fail;
}
}
}
if( r->playback_handle )
{
AUDIO_INITINFO(&pinfo);
pinfo.play.precision = 16;
pinfo.play.encoding = AUDIO_ENCODING_LINEAR;
pinfo.play.sample_rate = r->spsPlay;
pinfo.play.channels = r->channelsPlay;
if ( ioctl(r->playback_handle, AUDIO_SETINFO, &pinfo) < 0 )
{
fprintf (stderr, "cannot set audio playback format (%s)\n",
strerror (errno));
goto fail;
}
if ( ioctl(r->playback_handle, AUDIO_GETINFO, &pinfo) < 0 )
{
fprintf (stderr, "cannot get audio record format (%s)\n",
strerror (errno));
goto fail;
}
r->spsPlay = pinfo.play.sample_rate;
r->channelsPlay = pinfo.play.channels;
if ( (r->samplesPlay = calloc(2 * r->channelsPlay, r->bufsize)) == NULL )
{
goto fail;
}
}
if( r->record_handle )
{
AUDIO_INITINFO(&rinfo);
rinfo.record.precision = 16;
rinfo.record.encoding = AUDIO_ENCODING_LINEAR;
rinfo.record.sample_rate = r->spsRec;
rinfo.record.channels = r->channelsRec;
if ( ioctl(r->record_handle, AUDIO_SETINFO, &rinfo) < 0 )
{
fprintf (stderr, "cannot set audio record format (%s)\n",
strerror (errno));
goto fail;
}
if ( ioctl(r->record_handle, AUDIO_GETINFO, &rinfo) < 0 )
{
fprintf (stderr, "cannot get audio record format (%s)\n",
strerror (errno));
goto fail;
}
r->spsRec = rinfo.record.sample_rate;
r->channelsRec = rinfo.record.channels;
if ( (r->samplesRec = calloc(2 * r->channelsRec, r->bufsize)) == NULL )
{
goto fail;
}
}
if( r->playback_handle )
{
r->threadPlay = OGCreateThread( PlayThread, r );
}
if( r->record_handle )
{
r->threadRec = OGCreateThread( RecThread, r );
}
printf( "CNFA Sun Init Out -> channelsPlay: %d, spsPlay: %d, channelsRec: %d, spsRec: %d\n", r->channelsPlay, r->spsPlay, r->channelsRec, r->spsRec);
return r;
fail:
if( r )
{
if( r->playback_handle != -1 ) close (r->playback_handle);
if( r->record_handle != -1 ) close (r->record_handle);
free( r->samplesPlay );
free( r->samplesRec );
free( r );
}
return 0;
}
void * InitSunDriver( CNFACBType cb, const char * your_name, int reqSPSPlay, int reqSPSRec, int reqChannelsPlay, int reqChannelsRec, int sugBufferSize, const char * outputSelect, const char * inputSelect, void * opaque )
{
struct CNFADriverSun * r = (struct CNFADriverSun *)malloc( sizeof( struct CNFADriverSun ) );
r->CloseFn = CloseCNFASun;
r->StateFn = CNFAStateSun;
r->callback = cb;
r->opaque = opaque;
r->spsPlay = reqSPSPlay;
r->spsRec = reqSPSRec;
r->channelsPlay = reqChannelsPlay;
r->channelsRec = reqChannelsRec;
r->devRec = (inputSelect)?strdup(inputSelect):0;
r->devPlay = (outputSelect)?strdup(outputSelect):0;
r->samplesPlay = NULL;
r->samplesRec = NULL;
r->playback_handle = -1;
r->record_handle = -1;
r->bufsize = sugBufferSize;
return InitSun(r);
}
REGISTER_CNFA( SUN, 10, "Sun", InitSunDriver );

View file

@ -0,0 +1,514 @@
#include "CNFA.h"
//Needed libraries: -lmmdevapi -lavrt -lole32
//Or DLLs: C:/windows/system32/avrt.dll C:/windows/system32/ole32.dll
#ifdef TCC
#define NO_WIN_HEADERS
#endif
#ifdef NO_WIN_HEADERS
#include "CNFA_wasapi_utils.h"
#else
#include <InitGuid.h>
#include <audioclient.h> // Render and capturing audio
#include <mmdeviceapi.h> // Audio device handling
#include <Functiondiscoverykeys_devpkey.h> // Property keys for audio devices
#include <avrt.h> // Thread management
#include "windows.h"
#endif
#include "os_generic.h"
#if defined(WIN32) && !defined( TCC )
#pragma comment(lib,"avrt.lib")
#pragma comment(lib,"ole32.lib")
//And maybe mmdevapi.lib
#endif
#define WASAPIPRINT(message) (printf("[WASAPI] %s\n", message))
#define WASAPIERROR(error, message) (printf("[WASAPI][ERR] %s HRESULT: 0x%lX\n", message, error))
#define PRINTGUID(guid) (printf("{%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]))
#define WASAPI_EXTRA_DEBUG FALSE
// Forward declarations
void CloseCNFAWASAPI(void* stateObj);
int CNFAStateWASAPI(void* object);
static struct CNFADriverWASAPI* StartWASAPIDriver(struct CNFADriverWASAPI* initState);
static IMMDevice* WASAPIGetDefaultDevice(BOOL isCapture, BOOL isMultimedia);
static void WASAPIPrintAllDeviceLists();
static void WASAPIPrintDeviceList(EDataFlow dataFlow);
void* ProcessEventAudioIn(void* stateObj);
void* InitCNFAWASAPIDriver(
CNFACBType callback, const char *session_name,
int reqSampleRateOut, int reqSampleRateIn,
int reqChannelsOut, int reqChannelsIn, int sugBufferSize,
const char * inputDevice, const char * outputDevice,
void * opaque
);
DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xBCDE0395L, 0xE52F, 0x467C, 0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E);
DEFINE_GUID(IID_IMMDeviceEnumerator, 0xA95664D2L, 0x9614, 0x4F35, 0xA7, 0x46, 0xDE, 0x8D, 0xB6, 0x36, 0x17, 0xE6);
DEFINE_GUID(IID_IMMEndpoint, 0x1BE09788L, 0x6894, 0x4089, 0x85, 0x86, 0x9A, 0x2A, 0x6C, 0x26, 0x5A, 0xC5);
DEFINE_GUID(IID_IAudioClient, 0x1CB9AD4CL, 0xDBFA, 0x4c32, 0xB1, 0x78, 0xC2, 0xF5, 0x68, 0xA7, 0x03, 0xB2);
DEFINE_GUID(IID_IAudioCaptureClient, 0xC8ADBD64L, 0xE71E, 0x48a0, 0xA4, 0xDE, 0x18, 0x5C, 0x39, 0x5C, 0xD3, 0x17);
// This is a fallback if the client application does not provide a GUID.
DEFINE_GUID(CNFA_GUID, 0x899081C7L, 0x9428, 0x4103, 0x87, 0x93, 0x26, 0x47, 0xE5, 0xEA, 0xA2, 0xB4);
struct CNFADriverWASAPI
{
// Common CNFA items
void (*CloseFn)(void* object);
int (*StateFn)(void* object);
CNFACBType Callback;
short ChannelCountOut; // Not yet used.
short ChannelCountIn; // How many cahnnels the input stream has per frame. E.g. stereo = 2.
int SampleRateOut;
int SampleRateIn;
void* Opaque; // Not relevant to us
// Adjustable WASAPI-specific items
const char* SessionName; // The name to give our audio sessions. Otherwise, defaults to using embedded EXE name, Window title, or EXE file name directly.
const GUID* SessionID; // In order to have different CNFA-based applications individually controllable from the volume mixer, this should be set differently for every client program, but constant across all runs/builds of that application.
// Everything below here is for internal use only. Do not attempt to interact with these items.
const char* InputDeviceID; // The device to use for getting input from. Can be a render device (operating in loopback), or a capture device.
const char* OutputDeviceID; // Not yet used.
IMMDeviceEnumerator* DeviceEnumerator; // The base object that allows us to look through the system's devices, and from there get everything else.
IMMDevice* Device; // The device we are taking input from.
IAudioClient* Client; // The base client we use for getting input.
IAudioCaptureClient* CaptureClient; // The specific client we use for getting input.
WAVEFORMATEX* MixFormat; // The format of the input stream.
INT32 BytesPerFrame; // The number of bytes of one full frame of audio. AKA (channel count) * (sample bit depth), in Bytes.
BOOL StreamReady; // Whether the input stream is ready for data retrieval.
BOOL KeepGoing; // Whether to continue interacting with the streams, or shutdown the driver.
og_thread_t ThreadOut; // Not yet used.
og_thread_t ThreadIn; // The thread used to grab input data.
HANDLE EventHandleOut; // Not yet used.
HANDLE EventHandleIn; // The handle used to wait for more input data to be ready in the input thread.
HANDLE TaskHandleOut; // The task used to request output thread priority changes.
HANDLE TaskHandleIn; // The task used to request input thread priority changes.
};
// This is where the driver's current state is stored.
static struct CNFADriverWASAPI* WASAPIState;
// Stops streams, ends threads, and cleans up all resources used by the driver.
void CloseCNFAWASAPI(void* stateObj)
{
struct CNFADriverWASAPI* state = (struct CNFADriverWASAPI*)stateObj;
if(state != NULL)
{
// TODO: See if there are any other items that need cleanup.
state->KeepGoing = FALSE;
if (state->ThreadOut != NULL) { OGJoinThread(state->ThreadOut); }
if (state->ThreadIn != NULL) { OGJoinThread(state->ThreadIn); }
if (state->EventHandleOut != NULL) { CloseHandle(state->EventHandleOut); }
if (state->EventHandleIn != NULL) { CloseHandle(state->EventHandleIn); }
CoTaskMemFree(state->MixFormat);
if (state->CaptureClient != NULL) { state->CaptureClient->lpVtbl->Release(state->CaptureClient); }
if (state->Client != NULL) { state->Client->lpVtbl->Release(state->Client); }
if (state->Device != NULL) { state->Device->lpVtbl->Release(state->Device); }
if (state->DeviceEnumerator != NULL) { state->DeviceEnumerator->lpVtbl->Release(state->DeviceEnumerator); }
free(stateObj);
CoUninitialize();
printf("[WASAPI] Cleanup completed. Goodbye.\n");
}
}
// Gets the current state of the driver.
// 0 = No streams active
// 1 = Input stream active
// 2 = Output stream active
// 3 = Both streams active
int CNFAStateWASAPI(void* stateObj)
{
struct CNFADriverWASAPI* state = (struct CNFADriverWASAPI*)stateObj;
if(state != NULL)
{
if (state->StreamReady) { return 1; } // TODO: Output the correct status when output is implemented.
}
return 0;
}
// Reads the desired configuration, interfaces with WASAPI to get the current system information, and starts the input stream.
static struct CNFADriverWASAPI* StartWASAPIDriver(struct CNFADriverWASAPI* initState)
{
WASAPIState = initState;
WASAPIState->StreamReady = FALSE;
WASAPIState->SessionID = &CNFA_GUID;
HRESULT ErrorCode;
#ifndef BUILD_DLL
// A library should never call CoInitialize, as it needs to be done from the host program according to its threading model needs.
// NOTE: If you are getting errors, and you are using CNFA as a DLL, you need to call CoInitialize yourself with an appropriate threading model for your needs!
// When the host program is something like ColorChord on the other hand, it cannot be expected to call CoInitialize itself, so we do it on its behalf.
// This restricts the threading model of direct consumers of CNFA, but we can address that if it does ever become an issue.
ErrorCode = CoInitialize(NULL);
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "COM INIT FAILED!"); return WASAPIState; }
#endif
if(WASAPI_EXTRA_DEBUG)
{
printf("[WASAPI] CLSID for MMDeviceEnumerator: ");
PRINTGUID(CLSID_MMDeviceEnumerator);
printf("\n[WASAPI] IID for IMMDeviceEnumerator: ");
PRINTGUID(IID_IMMDeviceEnumerator);
printf("\n[WASAPI] IID for IAudioClient: ");
PRINTGUID(IID_IAudioClient);
printf("\n[WASAPI] IID for IAudioCaptureClient: ");
PRINTGUID(IID_IAudioCaptureClient);
printf("\n[WASAPI] IID for IMMEndpoint: ");
PRINTGUID(IID_IMMEndpoint);
printf("\n");
}
ErrorCode = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator, (void**)&(WASAPIState->DeviceEnumerator));
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get device enumerator. "); return WASAPIState; }
WASAPIPrintAllDeviceLists();
// We need to find the appropriate device to use.
BYTE DeviceDirection = 2; // 0 = Render, 1 = Capture, 2 = Unknown
if (WASAPIState->InputDeviceID == NULL)
{
WASAPIPRINT("No device specified, attempting to use system default multimedia capture device as input.");
WASAPIState->Device = WASAPIGetDefaultDevice(TRUE, TRUE);
DeviceDirection = 1;
}
else if (strcmp(WASAPIState->InputDeviceID, "defaultRender") == 0)
{
WASAPIPRINT("Attempting to use system default render device as input.");
WASAPIState->Device = WASAPIGetDefaultDevice(FALSE, TRUE);
DeviceDirection = 0;
}
else if (strncmp("defaultCapture", WASAPIState->InputDeviceID, strlen("defaultCapture")) == 0)
{
BOOL IsMultimedia = TRUE;
if (strstr(WASAPIState->InputDeviceID, "Comm") != NULL) { IsMultimedia = FALSE; }
printf("[WASAPI] Attempting to use system default %s capture device as input.\n", (IsMultimedia ? "multimedia" : "communications"));
WASAPIState->Device = WASAPIGetDefaultDevice(TRUE, IsMultimedia);
DeviceDirection = 1;
}
else // A specific device was selected by ID.
{
LPWSTR DeviceIDasLPWSTR;
DeviceIDasLPWSTR = malloc((strlen(WASAPIState->InputDeviceID) + 1) * sizeof(WCHAR));
mbstowcs(DeviceIDasLPWSTR, WASAPIState->InputDeviceID, strlen(WASAPIState->InputDeviceID) + 1);
printf("[WASAPI] Attempting to find specified device \"%ls\".\n", DeviceIDasLPWSTR);
ErrorCode = WASAPIState->DeviceEnumerator->lpVtbl->GetDevice(WASAPIState->DeviceEnumerator, DeviceIDasLPWSTR, &(WASAPIState->Device));
if (FAILED(ErrorCode))
{
WASAPIERROR(ErrorCode, "Failed to get audio device from the given ID. Using default multimedia capture device instead.");
WASAPIState->Device = WASAPIGetDefaultDevice(TRUE, TRUE);
DeviceDirection = 1;
}
else
{
printf("[WASAPI] Found specified device.\n");
DWORD DeviceState;
ErrorCode = WASAPIState->Device->lpVtbl->GetState(WASAPIState->Device, &DeviceState);
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get device state."); }
if ((DeviceState & DEVICE_STATE_DISABLED) == DEVICE_STATE_DISABLED) { WASAPIERROR(E_FAIL, "The specified device is currently disabled."); }
if ((DeviceState & DEVICE_STATE_NOTPRESENT) == DEVICE_STATE_NOTPRESENT) { WASAPIERROR(E_FAIL, "The specified device is not currently present."); }
if ((DeviceState & DEVICE_STATE_UNPLUGGED) == DEVICE_STATE_UNPLUGGED) { WASAPIERROR(E_FAIL, "The specified device is currently unplugged."); }
}
}
if (DeviceDirection == 2) // We still don't know what type of device we are trying to use. Query the endpoint to find out.
{
IMMEndpoint* Endpoint;
ErrorCode = WASAPIState->Device->lpVtbl->QueryInterface(WASAPIState->Device, &IID_IMMEndpoint, (void**)&Endpoint);
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get endpoint of device."); }
EDataFlow DataFlow;
ErrorCode = Endpoint->lpVtbl->GetDataFlow(Endpoint, &DataFlow);
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Could not determine endpoint type."); }
DeviceDirection = (DataFlow == eRender) ? 0 : 1;
if (Endpoint != NULL) { Endpoint->lpVtbl->Release(Endpoint); }
}
// We should have a device now.
char* DeviceDirectionDesc = (DeviceDirection == 0) ? "render" : ((DeviceDirection == 1) ? "capture" : "UNKNOWN");
LPWSTR DeviceID;
ErrorCode = WASAPIState->Device->lpVtbl->GetId(WASAPIState->Device, &DeviceID);
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get audio device ID."); return WASAPIState; }
else { printf("[WASAPI] Using device ID \"%ls\", which is a %s device.\n", DeviceID, DeviceDirectionDesc); }
// Start an audio client and get info about the stream format.
ErrorCode = WASAPIState->Device->lpVtbl->Activate(WASAPIState->Device, &IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&(WASAPIState->Client));
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get audio client. "); return WASAPIState; }
ErrorCode = WASAPIState->Client->lpVtbl->GetMixFormat(WASAPIState->Client, &(WASAPIState->MixFormat));
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get mix format. "); return WASAPIState; }
printf("[WASAPI] Mix format is %d channel, %luHz sample rate, %db per sample.\n", WASAPIState->MixFormat->nChannels, WASAPIState->MixFormat->nSamplesPerSec, WASAPIState->MixFormat->wBitsPerSample);
printf("[WASAPI] Mix format is format %d, %dB block-aligned, with %dB of extra data in this definition.\n", WASAPIState->MixFormat->wFormatTag, WASAPIState->MixFormat->nBlockAlign, WASAPIState->MixFormat->cbSize);
// We'll request PCM, 16bpS data from the system. It should be able to do this conversion for us, as long as we are not in exclusive mode.
// TODO: This isn't working, no matter what combination I try to ask it for. Figure this out, so we don't have to do the conversion ourselves.
// Also, we probably don't handle channel counts > 2 with this current setup.
//WASAPIState->MixFormat->wFormatTag = WAVE_FORMAT_PCM;
//WASAPIState->MixFormat->wBitsPerSample = 16 * WASAPIState->MixFormat->nChannels;
//WASAPIState->MixFormat->nBlockAlign = 2 * WASAPIState->MixFormat->nChannels;
//WASAPIState->MixFormat->nAvgBytesPerSec = WASAPIState->MixFormat->nSamplesPerSec * WASAPIState->MixFormat->nBlockAlign;
WASAPIState->ChannelCountIn = WASAPIState->MixFormat->nChannels;
WASAPIState->SampleRateIn = WASAPIState->MixFormat->nSamplesPerSec;
WASAPIState->BytesPerFrame = WASAPIState->MixFormat->nChannels * (WASAPIState->MixFormat->wBitsPerSample / 8);
REFERENCE_TIME DefaultInterval, MinimumInterval;
ErrorCode = WASAPIState->Client->lpVtbl->GetDevicePeriod(WASAPIState->Client, &DefaultInterval, &MinimumInterval);
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get device timing info. "); return WASAPIState; }
printf("[WASAPI] Default transaction period is %lld ticks, minimum is %lld ticks.\n", DefaultInterval, MinimumInterval);
// Configure a capture client.
UINT32 StreamFlags;
if (DeviceDirection == 1) { StreamFlags = AUDCLNT_STREAMFLAGS_NOPERSIST | AUDCLNT_STREAMFLAGS_EVENTCALLBACK; }
else if (DeviceDirection == 0) { StreamFlags = (AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_EVENTCALLBACK); }
else { WASAPIPRINT("[ERR] Device type was not determined!"); return WASAPIState; }
// TODO: Allow the target application to influence the interval we choose. Super realtime apps may require MinimumInterval.
ErrorCode = WASAPIState->Client->lpVtbl->Initialize(WASAPIState->Client, AUDCLNT_SHAREMODE_SHARED, StreamFlags, DefaultInterval, DefaultInterval, WASAPIState->MixFormat, WASAPIState->SessionID);
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Could not init audio client."); return WASAPIState; }
WASAPIState->EventHandleIn = CreateEvent(NULL, FALSE, FALSE, NULL);
if (WASAPIState->EventHandleIn == NULL) { WASAPIERROR(E_FAIL, "Failed to make event handle."); return WASAPIState; }
ErrorCode = WASAPIState->Client->lpVtbl->SetEventHandle(WASAPIState->Client, WASAPIState->EventHandleIn);
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to set event handler."); return WASAPIState; }
UINT32 BufferFrameCount;
ErrorCode = WASAPIState->Client->lpVtbl->GetBufferSize(WASAPIState->Client, &BufferFrameCount);
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Could not get audio client buffer size."); return WASAPIState; }
ErrorCode = WASAPIState->Client->lpVtbl->GetService(WASAPIState->Client, &IID_IAudioCaptureClient, (void**)&(WASAPIState->CaptureClient));
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Could not get audio capture client."); return WASAPIState; }
// Begin capturing audio. It will be received on a separate thread.
ErrorCode = WASAPIState->Client->lpVtbl->Start(WASAPIState->Client);
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Could not start audio client."); return WASAPIState; }
WASAPIState->StreamReady = TRUE;
WASAPIState->KeepGoing = TRUE;
WASAPIState->ThreadIn = OGCreateThread(ProcessEventAudioIn, WASAPIState);
return WASAPIState;
}
// Gets the default render or capture device.
// isCapture: If true, gets the default capture device, otherwise gets the default render device.
// isMultimedia: If true, gets the system default devide for "multimedia" use, otheriwse for "communication" use.
static IMMDevice* WASAPIGetDefaultDevice(BOOL isCapture, BOOL isMultimedia)
{
HRESULT ErrorCode;
IMMDevice* Device;
ErrorCode = WASAPIState->DeviceEnumerator->lpVtbl->GetDefaultAudioEndpoint(WASAPIState->DeviceEnumerator, isCapture ? eCapture : eRender, isMultimedia ? eMultimedia : eCommunications, &Device);
if (FAILED(ErrorCode))
{
WASAPIERROR(ErrorCode, "Failed to get default device.");
return NULL;
}
return Device;
}
// Prints all available devices to the console.
static void WASAPIPrintAllDeviceLists()
{
WASAPIPrintDeviceList(eRender);
WASAPIPrintDeviceList(eCapture);
}
// Prints a list of all available devices of a specified data flow direction to the console.
static void WASAPIPrintDeviceList(EDataFlow dataFlow)
{
printf("[WASAPI] %s Devices:\n", (dataFlow == eCapture ? "Capture" : "Render"));
IMMDeviceCollection* Devices;
HRESULT ErrorCode = WASAPIState->DeviceEnumerator->lpVtbl->EnumAudioEndpoints(WASAPIState->DeviceEnumerator, dataFlow, (WASAPI_EXTRA_DEBUG ? DEVICE_STATEMASK_ALL : DEVICE_STATE_ACTIVE), &Devices);
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get audio endpoints."); return; }
UINT32 DeviceCount;
ErrorCode = Devices->lpVtbl->GetCount(Devices, &DeviceCount);
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get audio endpoint count."); return; }
for (UINT32 DeviceIndex = 0; DeviceIndex < DeviceCount; DeviceIndex++)
{
IMMDevice* Device;
ErrorCode = Devices->lpVtbl->Item(Devices, DeviceIndex, &Device);
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get audio device."); continue; }
LPWSTR DeviceID;
ErrorCode = Device->lpVtbl->GetId(Device, &DeviceID);
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get audio device ID."); continue; }
IPropertyStore* Properties;
ErrorCode = Device->lpVtbl->OpenPropertyStore(Device, STGM_READ, &Properties);
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get device properties."); continue; }
PROPVARIANT Variant;
PropVariantInit(&Variant);
ErrorCode = Properties->lpVtbl->GetValue(Properties, &PKEY_Device_FriendlyName, &Variant);
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get device friendly name."); }
LPWSTR DeviceFriendlyName = L"[Name Retrieval Failed]";
if (Variant.pwszVal != NULL) { DeviceFriendlyName = Variant.pwszVal; }
printf("[WASAPI] [%d]: \"%ls\" = \"%ls\"\n", DeviceIndex, DeviceFriendlyName, DeviceID);
CoTaskMemFree(DeviceID);
DeviceID = NULL;
PropVariantClear(&Variant);
if (Properties != NULL) { Properties->lpVtbl->Release(Properties); }
if (Device != NULL) { Device->lpVtbl->Release(Device); }
}
if (Devices != NULL) { Devices->lpVtbl->Release(Devices); }
}
// Runs on a thread. Waits for audio data to be ready from the system, then forwards it to the registered callback.
void* ProcessEventAudioIn(void* stateObj)
{
struct CNFADriverWASAPI* state = (struct CNFADriverWASAPI*)stateObj;
HRESULT ErrorCode;
UINT32 PacketLength;
// TODO: Set this based on our device period requested. If we are using 10ms or higher, just request "Audio", not "Pro Audio".
DWORD TaskIndex = 0;
state->TaskHandleIn = AvSetMmThreadCharacteristicsW(L"Pro Audio", &TaskIndex);
if (state->TaskHandleIn == NULL) { WASAPIERROR(E_FAIL, "Failed to request thread priority elevation on input task."); }
while (state->KeepGoing)
{
// Waits up to 500ms to get the next audio buffer from the system.
// The timeout is used because if no audio sessions are active, WASAPI stops sending buffers after a few that indicate silence.
// This means that if the client tries to exit, this loop would not complete, and therefore the thread would not exit, until the next buffer is received.
// This is mostly an issue in loopback mode, where true silence is common, not so much on microphones.
DWORD WaitResult = WaitForSingleObject(state->EventHandleIn, 500);
if (WaitResult == WAIT_TIMEOUT) { continue; } // We are in a period of silence. Keep waiting for audio.
else if (WaitResult != WAIT_OBJECT_0) { WASAPIERROR(E_FAIL, "Something went wrong while waiting for an audio event."); continue; }
ErrorCode = state->CaptureClient->lpVtbl->GetNextPacketSize(state->CaptureClient, &PacketLength);
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get audio packet size."); continue; }
BYTE* DataBuffer;
UINT32 FramesAvailable;
DWORD BufferStatus;
BOOL Released = FALSE;
ErrorCode = state->CaptureClient->lpVtbl->GetBuffer(state->CaptureClient, &DataBuffer, &FramesAvailable, &BufferStatus, NULL, NULL);
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get audio buffer."); continue; }
// "The data in the packet is not correlated with the previous packet's device position; this is possibly due to a stream state transition or timing glitch."
// There's no real way for us to notify the client about this...
if ((BufferStatus & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) == AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY)
{
WASAPIPRINT("A data discontinuity was detected.");
}
if ((BufferStatus & AUDCLNT_BUFFERFLAGS_SILENT) == AUDCLNT_BUFFERFLAGS_SILENT)
{
UINT32 Length = FramesAvailable * state->MixFormat->nChannels;
if (Length == 0) { Length = state->MixFormat->nChannels; }
INT16* AudioData = malloc(Length * 2);
for (int i = 0; i < Length; i++) { AudioData[i] = 0; }
ErrorCode = state->CaptureClient->lpVtbl->ReleaseBuffer(state->CaptureClient, FramesAvailable);
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to release audio buffer."); }
else { Released = TRUE; }
if (WASAPI_EXTRA_DEBUG) { printf("[WASAPI] SILENCE buffer received. Passing on %d samples.\n", Length); }
WASAPIState->Callback((struct CNFADriver*)WASAPIState, 0, AudioData, 0, Length / state->MixFormat->nChannels );
free(AudioData);
}
else
{
// TODO: This assumes that data is coming in at 32b float format. While this appears to be the format that WASAPI uses internally in all cases I've seen, I don't think it's guaranteed.
// We should instead read the MixFormat information and properly handle the data in other cases.
// Ideally, we could request 16b signed PCM data from WASAPI, so we don't even have to do any conversion. But I couldn't get this working yet.
UINT32 Size = FramesAvailable * state->BytesPerFrame; // Size in bytes
FLOAT* DataAsFloat = (FLOAT*)DataBuffer; // The raw input data, reinterpreted as floats.
INT16* AudioData = malloc((FramesAvailable * state->MixFormat->nChannels) * 2); // The data we are passing to the consumer.
for (INT32 i = 0; i < Size / 4; i++) { AudioData[i] = (INT16)(DataAsFloat[i] * 32767.5F); }
ErrorCode = state->CaptureClient->lpVtbl->ReleaseBuffer(state->CaptureClient, FramesAvailable);
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to release audio buffer."); }
else { Released = TRUE; }
if (WASAPI_EXTRA_DEBUG) { printf("[WASAPI] Got %d bytes of audio data in %d frames. Fowarding to %p.\n", Size, FramesAvailable, (void*) WASAPIState->Callback); }
WASAPIState->Callback((struct CNFADriver*)WASAPIState, 0, AudioData, 0, FramesAvailable );
free(AudioData);
}
if (!Released)
{
ErrorCode = state->CaptureClient->lpVtbl->ReleaseBuffer(state->CaptureClient, FramesAvailable);
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to release audio buffer."); }
}
}
ErrorCode = state->Client->lpVtbl->Stop(state->Client);
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to stop audio client."); }
if(state->TaskHandleIn != NULL) { AvRevertMmThreadCharacteristics(state->TaskHandleIn); }
state->StreamReady = FALSE;
return 0;
}
// Begins preparation of the WASAPI driver.
// callback: The user application's function where audio data is placed when received from the system and/or audio data is retrieved from to give to the system.
// sessionName: How your session will appear to the end user if you play audio.
// reqSampleRateIn/Out: Sample rate you'd like to request. Ignored, as this is determined by the system. See note below.
// reqChannelsIn: Input channel count you'd like to request. Ignored, as this is determined by the system. See note below.
// reqChannelsOut: Output channel count you'd like to request. Ignored, as this is determined by the system. See note below.
// sugBufferSize: Buffer size you'd like to request. Ignored, as this is determined by the system. See note below.
// inputDevice: The device you want to receive audio from. Loopback is supported, so this can be either a capture or render device.
// To get the default render device, specify "defaultRender"
// To get the default multimedia capture device, specify "defaultCapture"
// To get the default communications capture device, specify "defaultCaptureComm"
// A device ID as presented by WASAPI can be specified, regardless of what type it is. If it is invalid, the default capture device is used as fallback.
// If you do not wish to receive audio, specify null. NOT YET IMPLEMENTED
// outputDevice: The device you want to output audio to. OUTPUT IS NOT IMPLEMENTED.
// NOTES:
// Regarding format requests: Sample rate and channel count is determined by the system settings, and cannot be changed. Resampling/mixing will be required in your application if you cannot accept the current system mode. Make sure to check `WASAPIState` for the current system mode.
// Note also that both sample rate and channel count can vary between input and output!
// Currently audio output (playing) is not yet implemented.
void* InitCNFAWASAPIDriver(
CNFACBType callback, const char *sessionName,
int reqSampleRateOut, int reqSampleRateIn,
int reqChannelsOut, int reqChannelsIn, int sugBufferSize,
const char * outputDevice, const char * inputDevice,
void * opaque)
{
struct CNFADriverWASAPI * InitState = malloc(sizeof(struct CNFADriverWASAPI));
memset(InitState, 0, sizeof(*InitState));
InitState->CloseFn = CloseCNFAWASAPI;
InitState->StateFn = CNFAStateWASAPI;
InitState->Callback = callback;
InitState->Opaque = opaque;
// TODO: Waiting for CNFA to support directional sample rates.
InitState->SampleRateIn = reqSampleRateIn; // Will be overridden by the actual system setting.
InitState->SampleRateOut = reqSampleRateOut; // Will be overridden by the actual system setting.
InitState->ChannelCountIn = reqChannelsIn; // Will be overridden by the actual system setting.
InitState->ChannelCountOut = reqChannelsOut; // Will be overridden by the actual system setting.
InitState->InputDeviceID = inputDevice;
InitState->OutputDeviceID = outputDevice;
InitState->SessionName = sessionName;
WASAPIPRINT("WASAPI Init");
return StartWASAPIDriver(InitState);
}
REGISTER_CNFA(cnfa_wasapi, 20, "WASAPI", InitCNFAWASAPIDriver);

View file

@ -0,0 +1,636 @@
#ifndef _CNFA_WASAPI_UTILS_H
#define _CNFA_WASAPI_UTILS_H
//#include "ole2.h"
#ifndef REFPROPERTYKEY
#define REFPROPERTYKEY const PROPERTYKEY * __MIDL_CONST
#endif //REFPROPERTYKEY
// Necessary definitions
#define _ANONYMOUS_STRUCT
#define BEGIN_INTERFACE
#define END_INTERFACE
#define DEVICE_STATE_ACTIVE 0x00000001
#define AUDCLNT_STREAMFLAGS_CROSSPROCESS 0x00010000
#define AUDCLNT_STREAMFLAGS_LOOPBACK 0x00020000
#define AUDCLNT_STREAMFLAGS_EVENTCALLBACK 0x00040000
#define AUDCLNT_STREAMFLAGS_NOPERSIST 0x00080000
#define AUDCLNT_STREAMFLAGS_RATEADJUST 0x00100000
#define AUDCLNT_STREAMFLAGS_PREVENT_LOOPBACK_CAPTURE 0x01000000
#define AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY 0x08000000
#define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000
#define AUDCLNT_SESSIONFLAGS_EXPIREWHENUNOWNED 0x10000000
#define AUDCLNT_SESSIONFLAGS_DISPLAY_HIDE 0x20000000
#define AUDCLNT_SESSIONFLAGS_DISPLAY_HIDEWHENEXPIRED 0x40000000
enum _AUDCLNT_BUFFERFLAGS
{
AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY = 0x1,
AUDCLNT_BUFFERFLAGS_SILENT = 0x2,
AUDCLNT_BUFFERFLAGS_TIMESTAMP_ERROR = 0x4
} ;
#ifndef REFIID
#define REFIID const IID * __MIDL_CONST
#endif
#ifndef PropVariantInit
#define PropVariantInit(pvar) memset ( (pvar), 0, sizeof(PROPVARIANT) )
#endif
#if defined (__TINYC__)
#define _COM_Outptr_
#define _In_
#define _Out_
#define _Outptr_
#define _In_opt_
#define _Out_opt_
#define __RPC__in
#define __RPC__out
#define interface struct
#define CONST_VTBL
#define _Outptr_result_buffer_(X)
#define _Inexpressible_(X)
#define REFPROPVARIANT const PROPVARIANT * __MIDL_CONST
typedef struct tagPROPVARIANT PROPVARIANT;
typedef struct tWAVEFORMATEX WAVEFORMATEX;
typedef IID GUID;
typedef void* HANDLE;
#define CLSCTX_INPROC_SERVER 0x1
#define CLSCTX_INPROC_HANDLER 0x2
#define CLSCTX_LOCAL_SERVER 0x4
#define CLSCTX_REMOTE_SERVER 0x10
#define STGM_READ 0x00000000L
#define CLSCTX_ALL (CLSCTX_INPROC_SERVER| \
CLSCTX_INPROC_HANDLER| \
CLSCTX_LOCAL_SERVER| \
CLSCTX_REMOTE_SERVER)
typedef unsigned short VARTYPE;
typedef struct _tagpropertykey {
GUID fmtid;
DWORD pid;
} PROPERTYKEY;
#ifndef __wtypes_h__
typedef struct tagDEC {
USHORT wReserved;
BYTE scale;
BYTE sign;
ULONG Hi32;
ULONGLONG Lo64;
} DECIMAL;
// Property varient struct, used for getting the device name info
typedef BYTE PROPVAR_PAD1;
typedef BYTE PROPVAR_PAD2;
typedef ULONG PROPVAR_PAD3;
struct tagPROPVARIANT {
union {
struct tag_inner_PROPVARIANT
{
VARTYPE vt;
PROPVAR_PAD1 wReserved1;
PROPVAR_PAD2 wReserved2;
PROPVAR_PAD3 wReserved3;
union
{
double dblVal; // Filler for the largest object we need to store
LPWSTR pwszVal; // This is the only parameter we actually use
};
} ;
DECIMAL decVal;
};
};
#endif
#define _Inout_updates_(dwCount)
#define FAR
typedef interface IUnknown IUnknown;
typedef IUnknown *LPUNKNOWN;
#endif
#ifdef NO_WIN_HEADERS
#undef DEFINE_GUID
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
EXTERN_C const GUID DECLSPEC_SELECTANY name \
= { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
#undef DEFINE_PROPERTYKEY
#define DEFINE_PROPERTYKEY(name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8,pid) \
EXTERN_C const PROPERTYKEY DECLSPEC_SELECTANY name \
= { { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }, pid }
// stuff to be able to read device names
DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14);
#ifndef WINOLEAPI
#define WINOLEAPI EXTERN_C DECLSPEC_IMPORT HRESULT STDAPICALLTYPE
#define WINOLEAPI_(type) EXTERN_C DECLSPEC_IMPORT type STDAPICALLTYPE
#endif
// Define necessary functions
WINOLEAPI_(HANDLE)
AvSetMmThreadCharacteristicsW(LPCWSTR TaskName, LPDWORD TaskIndex);
WINOLEAPI_(BOOL)
AvRevertMmThreadCharacteristics(HANDLE AvrtHandle);
WINOLEAPI CoInitialize(LPVOID pvReserved);
WINOLEAPI_(void) CoUninitialize();
WINOLEAPI_(void) CoTaskMemFree(LPVOID pv);
WINOLEAPI CoCreateInstance(
REFCLSID rclsid,
LPUNKNOWN pUnkOuter,
DWORD dwClsContext,
REFIID riid,
LPVOID FAR* ppv);
// WINOLEAPI CoCreateInstanceEx(
// REFCLSID Clsid,
// IUnknown *punkOuter,
// DWORD dwClsCtx,
// COSERVERINFO *pServerInfo,
// DWORD dwCount,
// MULTI_QI *pResults );
#endif //NO_WIN_HEADERS
// forward declarations
typedef struct IMMDevice IMMDevice;
typedef struct IMMDeviceCollection IMMDeviceCollection;
typedef struct IMMDeviceEnumerator IMMDeviceEnumerator;
typedef struct IMMNotificationClient IMMNotificationClient;
typedef struct IPropertyStore IPropertyStore;
typedef struct IAudioClient IAudioClient;
typedef struct IAudioCaptureClient IAudioCaptureClient;
// So the linker doesn't complain
extern const IID CLSID_MMDeviceEnumerator;
extern const IID IID_IMMDeviceEnumerator;
extern const IID IID_IAudioClient;
extern const IID CNFA_GUID;
extern const IID IID_IAudioCaptureClient;
typedef enum __MIDL___MIDL_itf_mmdeviceapi_0000_0000_0001
{
eRender = 0,
eCapture = ( eRender + 1 ) ,
eAll = ( eCapture + 1 ) ,
EDataFlow_enum_count = ( eAll + 1 )
} EDataFlow;
typedef enum __MIDL___MIDL_itf_mmdeviceapi_0000_0000_0002
{
eConsole = 0,
eMultimedia = ( eConsole + 1 ) ,
eCommunications = ( eMultimedia + 1 ) ,
ERole_enum_count = ( eCommunications + 1 )
} ERole;
typedef struct IMMDeviceEnumeratorVtbl
{
BEGIN_INTERFACE
HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
IMMDeviceEnumerator * This,
/* [in] */ REFIID riid,
/* [annotation][iid_is][out] */
_COM_Outptr_ void **ppvObject);
ULONG ( STDMETHODCALLTYPE *AddRef )(
IMMDeviceEnumerator * This);
ULONG ( STDMETHODCALLTYPE *Release )(
IMMDeviceEnumerator * This);
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *EnumAudioEndpoints )(
IMMDeviceEnumerator * This,
/* [annotation][in] */
_In_ EDataFlow dataFlow,
/* [annotation][in] */
_In_ DWORD dwStateMask,
/* [annotation][out] */
_Out_ IMMDeviceCollection **ppDevices);
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *GetDefaultAudioEndpoint )(
IMMDeviceEnumerator * This,
/* [annotation][in] */
_In_ EDataFlow dataFlow,
/* [annotation][in] */
_In_ ERole role,
/* [annotation][out] */
_Out_ IMMDevice **ppEndpoint);
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *GetDevice )(
IMMDeviceEnumerator * This,
/* [annotation][in] */
_In_ LPCWSTR pwstrId,
/* [annotation][out] */
_Out_ IMMDevice **ppDevice);
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *RegisterEndpointNotificationCallback )(
IMMDeviceEnumerator * This,
/* [annotation][in] */
_In_ IMMNotificationClient *pClient);
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *UnregisterEndpointNotificationCallback )(
IMMDeviceEnumerator * This,
/* [annotation][in] */
_In_ IMMNotificationClient *pClient);
END_INTERFACE
} IMMDeviceEnumeratorVtbl;
interface IMMDeviceEnumerator
{
CONST_VTBL struct IMMDeviceEnumeratorVtbl *lpVtbl;
};
typedef struct IMMDeviceCollectionVtbl
{
BEGIN_INTERFACE
HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
IMMDeviceCollection * This,
/* [in] */ REFIID riid,
/* [annotation][iid_is][out] */
_COM_Outptr_ void **ppvObject);
ULONG ( STDMETHODCALLTYPE *AddRef )(
IMMDeviceCollection * This);
ULONG ( STDMETHODCALLTYPE *Release )(
IMMDeviceCollection * This);
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *GetCount )(
IMMDeviceCollection * This,
/* [annotation][out] */
_Out_ UINT *pcDevices);
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *Item )(
IMMDeviceCollection * This,
/* [annotation][in] */
_In_ UINT nDevice,
/* [annotation][out] */
_Out_ IMMDevice **ppDevice);
END_INTERFACE
} IMMDeviceCollectionVtbl;
interface IMMDeviceCollection
{
CONST_VTBL struct IMMDeviceCollectionVtbl *lpVtbl;
};
typedef struct IMMDeviceVtbl
{
BEGIN_INTERFACE
HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
IMMDevice * This,
/* [in] */ REFIID riid,
/* [annotation][iid_is][out] */
_COM_Outptr_ void **ppvObject);
ULONG ( STDMETHODCALLTYPE *AddRef )(
IMMDevice * This);
ULONG ( STDMETHODCALLTYPE *Release )(
IMMDevice * This);
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *Activate )(
IMMDevice * This,
/* [annotation][in] */
_In_ REFIID iid,
/* [annotation][in] */
_In_ DWORD dwClsCtx,
/* [annotation][unique][in] */
_In_opt_ PROPVARIANT *pActivationParams,
/* [annotation][iid_is][out] */
_Out_ void **ppInterface);
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *OpenPropertyStore )(
IMMDevice * This,
/* [annotation][in] */
_In_ DWORD stgmAccess,
/* [annotation][out] */
_Out_ IPropertyStore **ppProperties);
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *GetId )(
IMMDevice * This,
/* [annotation][out] */
_Outptr_ LPWSTR *ppstrId);
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *GetState )(
IMMDevice * This,
/* [annotation][out] */
_Out_ DWORD *pdwState);
END_INTERFACE
} IMMDeviceVtbl;
interface IMMDevice
{
CONST_VTBL struct IMMDeviceVtbl *lpVtbl;
};
typedef struct IMMNotificationClientVtbl
{
BEGIN_INTERFACE
HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
IMMNotificationClient * This,
/* [in] */ REFIID riid,
/* [annotation][iid_is][out] */
_COM_Outptr_ void **ppvObject);
ULONG ( STDMETHODCALLTYPE *AddRef )(
IMMNotificationClient * This);
ULONG ( STDMETHODCALLTYPE *Release )(
IMMNotificationClient * This);
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *OnDeviceStateChanged )(
IMMNotificationClient * This,
/* [annotation][in] */
_In_ LPCWSTR pwstrDeviceId,
/* [annotation][in] */
_In_ DWORD dwNewState);
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *OnDeviceAdded )(
IMMNotificationClient * This,
/* [annotation][in] */
_In_ LPCWSTR pwstrDeviceId);
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *OnDeviceRemoved )(
IMMNotificationClient * This,
/* [annotation][in] */
_In_ LPCWSTR pwstrDeviceId);
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *OnDefaultDeviceChanged )(
IMMNotificationClient * This,
/* [annotation][in] */
_In_ EDataFlow flow,
/* [annotation][in] */
_In_ ERole role,
/* [annotation][in] */
_In_ LPCWSTR pwstrDefaultDeviceId);
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *OnPropertyValueChanged )(
IMMNotificationClient * This,
/* [annotation][in] */
_In_ LPCWSTR pwstrDeviceId,
/* [annotation][in] */
_In_ const PROPERTYKEY key);
END_INTERFACE
} IMMNotificationClientVtbl;
interface IMMNotificationClient
{
CONST_VTBL struct IMMNotificationClientVtbl *lpVtbl;
};
typedef struct IPropertyStoreVtbl
{
BEGIN_INTERFACE
HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
__RPC__in IPropertyStore * This,
/* [in] */ __RPC__in REFIID riid,
/* [annotation][iid_is][out] */
_COM_Outptr_ void **ppvObject);
ULONG ( STDMETHODCALLTYPE *AddRef )(
__RPC__in IPropertyStore * This);
ULONG ( STDMETHODCALLTYPE *Release )(
__RPC__in IPropertyStore * This);
HRESULT ( STDMETHODCALLTYPE *GetCount )(
__RPC__in IPropertyStore * This,
/* [out] */ __RPC__out DWORD *cProps);
HRESULT ( STDMETHODCALLTYPE *GetAt )(
__RPC__in IPropertyStore * This,
/* [in] */ DWORD iProp,
/* [out] */ __RPC__out PROPERTYKEY *pkey);
HRESULT ( STDMETHODCALLTYPE *GetValue )(
__RPC__in IPropertyStore * This,
/* [in] */ __RPC__in REFPROPERTYKEY key,
/* [out] */ __RPC__out PROPVARIANT *pv);
HRESULT ( STDMETHODCALLTYPE *SetValue )(
__RPC__in IPropertyStore * This,
/* [in] */ __RPC__in REFPROPERTYKEY key,
/* [in] */ __RPC__in REFPROPVARIANT propvar);
HRESULT ( STDMETHODCALLTYPE *Commit )(
__RPC__in IPropertyStore * This);
END_INTERFACE
} IPropertyStoreVtbl;
interface IPropertyStore
{
CONST_VTBL struct IPropertyStoreVtbl *lpVtbl;
};
// ----- audioclient.h -----
typedef enum _AUDCLNT_SHAREMODE
{
AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_SHAREMODE_EXCLUSIVE
} AUDCLNT_SHAREMODE;
typedef LONGLONG REFERENCE_TIME;
typedef struct IAudioClientVtbl
{
BEGIN_INTERFACE
HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
IAudioClient * This,
/* [in] */ REFIID riid,
/* [annotation][iid_is][out] */
_COM_Outptr_ void **ppvObject);
ULONG ( STDMETHODCALLTYPE *AddRef )(
IAudioClient * This);
ULONG ( STDMETHODCALLTYPE *Release )(
IAudioClient * This);
HRESULT ( STDMETHODCALLTYPE *Initialize )(
IAudioClient * This,
/* [annotation][in] */
_In_ AUDCLNT_SHAREMODE ShareMode,
/* [annotation][in] */
_In_ DWORD StreamFlags,
/* [annotation][in] */
_In_ REFERENCE_TIME hnsBufferDuration,
/* [annotation][in] */
_In_ REFERENCE_TIME hnsPeriodicity,
/* [annotation][in] */
_In_ const WAVEFORMATEX *pFormat,
/* [annotation][in] */
_In_opt_ LPCGUID AudioSessionGuid);
HRESULT ( STDMETHODCALLTYPE *GetBufferSize )(
IAudioClient * This,
/* [annotation][out] */
_Out_ UINT32 *pNumBufferFrames);
HRESULT ( STDMETHODCALLTYPE *GetStreamLatency )(
IAudioClient * This,
/* [annotation][out] */
_Out_ REFERENCE_TIME *phnsLatency);
HRESULT ( STDMETHODCALLTYPE *GetCurrentPadding )(
IAudioClient * This,
/* [annotation][out] */
_Out_ UINT32 *pNumPaddingFrames);
HRESULT ( STDMETHODCALLTYPE *IsFormatSupported )(
IAudioClient * This,
/* [annotation][in] */
_In_ AUDCLNT_SHAREMODE ShareMode,
/* [annotation][in] */
_In_ const WAVEFORMATEX *pFormat,
/* [unique][annotation][out] */
_Out_opt_ WAVEFORMATEX **ppClosestMatch);
HRESULT ( STDMETHODCALLTYPE *GetMixFormat )(
IAudioClient * This,
/* [annotation][out] */
_Out_ WAVEFORMATEX **ppDeviceFormat);
HRESULT ( STDMETHODCALLTYPE *GetDevicePeriod )(
IAudioClient * This,
/* [annotation][out] */
_Out_opt_ REFERENCE_TIME *phnsDefaultDevicePeriod,
/* [annotation][out] */
_Out_opt_ REFERENCE_TIME *phnsMinimumDevicePeriod);
HRESULT ( STDMETHODCALLTYPE *Start )(
IAudioClient * This);
HRESULT ( STDMETHODCALLTYPE *Stop )(
IAudioClient * This);
HRESULT ( STDMETHODCALLTYPE *Reset )(
IAudioClient * This);
HRESULT ( STDMETHODCALLTYPE *SetEventHandle )(
IAudioClient * This,
/* [in] */ HANDLE eventHandle);
HRESULT ( STDMETHODCALLTYPE *GetService )(
IAudioClient * This,
/* [annotation][in] */
_In_ REFIID riid,
/* [annotation][iid_is][out] */
_Out_ void **ppv);
END_INTERFACE
} IAudioClientVtbl;
interface IAudioClient
{
CONST_VTBL struct IAudioClientVtbl *lpVtbl;
};
typedef struct IAudioCaptureClientVtbl
{
BEGIN_INTERFACE
HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
IAudioCaptureClient * This,
/* [in] */ REFIID riid,
/* [annotation][iid_is][out] */
_COM_Outptr_ void **ppvObject);
ULONG ( STDMETHODCALLTYPE *AddRef )(
IAudioCaptureClient * This);
ULONG ( STDMETHODCALLTYPE *Release )(
IAudioCaptureClient * This);
HRESULT ( STDMETHODCALLTYPE *GetBuffer )(
IAudioCaptureClient * This,
/* [annotation][out] */
_Outptr_result_buffer_(_Inexpressible_("*pNumFramesToRead * pFormat->nBlockAlign")) BYTE **ppData,
/* [annotation][out] */
_Out_ UINT32 *pNumFramesToRead,
/* [annotation][out] */
_Out_ DWORD *pdwFlags,
/* [annotation][unique][out] */
_Out_opt_ UINT64 *pu64DevicePosition,
/* [annotation][unique][out] */
_Out_opt_ UINT64 *pu64QPCPosition);
HRESULT ( STDMETHODCALLTYPE *ReleaseBuffer )(
IAudioCaptureClient * This,
/* [annotation][in] */
_In_ UINT32 NumFramesRead);
HRESULT ( STDMETHODCALLTYPE *GetNextPacketSize )(
IAudioCaptureClient * This,
/* [annotation][out] */
_Out_ UINT32 *pNumFramesInNextPacket);
END_INTERFACE
} IAudioCaptureClientVtbl;
interface IAudioCaptureClient
{
CONST_VTBL struct IAudioCaptureClientVtbl *lpVtbl;
};
typedef interface IMMEndpoint IMMEndpoint;
typedef struct IMMEndpointVtbl
{
BEGIN_INTERFACE
HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
IMMEndpoint * This,
/* [in] */ REFIID riid,
/* [annotation][iid_is][out] */
_COM_Outptr_ void **ppvObject);
ULONG ( STDMETHODCALLTYPE *AddRef )(
IMMEndpoint * This);
ULONG ( STDMETHODCALLTYPE *Release )(
IMMEndpoint * This);
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *GetDataFlow )(
IMMEndpoint * This,
/* [annotation][out] */
_Out_ EDataFlow *pDataFlow);
END_INTERFACE
} IMMEndpointVtbl;
interface IMMEndpoint
{
CONST_VTBL struct IMMEndpointVtbl *lpVtbl;
};
#define DEVICE_STATE_ACTIVE 0x00000001
#define DEVICE_STATE_DISABLED 0x00000002
#define DEVICE_STATE_NOTPRESENT 0x00000004
#define DEVICE_STATE_UNPLUGGED 0x00000008
#define DEVICE_STATEMASK_ALL 0x0000000f
#endif // _CNFA_WASAPI_UTILS_H

View file

@ -0,0 +1,258 @@
//Copyright 2015-2020 <>< Charles Lohr under the ColorChord License, MIT/x11 license or NewBSD Licenses.
#include <windows.h>
#include "CNFA.h"
#include "os_generic.h"
#include <stdio.h>
#include <stdint.h>
#include <mmsystem.h>
#include <stdlib.h>
//Include -lwinmm, or, C:/windows/system32/winmm.dll
#if defined(WINDOWS) || defined(WIN32) || defined(WIN64) \
|| defined(_WIN32) || defined(_WIN64)
#ifndef strdup
#define strdup _strdup
#endif
#endif
#if defined(WIN32) && !defined( TCC )
#pragma comment(lib,"winmm.lib")
#endif
#define BUFFS 3
struct CNFADriverWin
{
//Standard header - must remain.
void (*CloseFn)( void * object );
int (*StateFn)( void * object );
CNFACBType callback;
short channelsPlay;
short channelsRec;
int spsPlay;
int spsRec;
void * opaque;
char * sInputDev;
char * sOutputDev;
int buffer;
int isEnding;
int GOBUFFRec;
int GOBUFFPlay;
int recording;
int playing;
HWAVEIN hMyWaveIn;
HWAVEOUT hMyWaveOut;
WAVEHDR WavBuffIn[BUFFS];
WAVEHDR WavBuffOut[BUFFS];
};
static struct CNFADriverWin * w;
void CloseCNFAWin( void * v )
{
struct CNFADriverWin * r = (struct CNFADriverWin *)v;
int i;
if( r )
{
if( r->hMyWaveIn )
{
waveInStop(r->hMyWaveIn);
waveInReset(r->hMyWaveIn);
for ( i=0;i<BUFFS;i++)
{
waveInUnprepareHeader(r->hMyWaveIn,&(r->WavBuffIn[i]),sizeof(WAVEHDR));
free ((r->WavBuffIn[i]).lpData);
}
waveInClose(r->hMyWaveIn);
}
if( r->hMyWaveOut )
{
waveOutPause(r->hMyWaveOut);
waveOutReset(r->hMyWaveOut);
for ( i=0;i<BUFFS;i++)
{
waveInUnprepareHeader(r->hMyWaveIn,&(r->WavBuffOut[i]),sizeof(WAVEHDR));
free ((r->WavBuffOut[i]).lpData);
}
waveInClose(r->hMyWaveIn);
waveOutClose(r->hMyWaveOut);
}
free( r );
}
}
int CNFAStateWin( void * v )
{
struct CNFADriverWin * soundobject = (struct CNFADriverWin *)v;
return soundobject->recording | (soundobject->playing?2:0);
}
void CALLBACK HANDLEMIC(HWAVEIN hwi, UINT umsg, DWORD dwi, DWORD hdr, DWORD dwparm)
{
int ctr;
int ob;
long cValue;
unsigned int maxWave=0;
if (w->isEnding) return;
switch (umsg)
{
case MM_WIM_OPEN:
printf( "Mic Open.\n" );
w->recording = 1;
break;
case MM_WIM_DATA:
ob = (w->GOBUFFRec+(BUFFS))%BUFFS;
w->callback( (struct CNFADriver*)w, 0, (short*)(w->WavBuffIn[w->GOBUFFRec]).lpData, 0, w->buffer );
waveInAddBuffer(w->hMyWaveIn,&(w->WavBuffIn[w->GOBUFFRec]),sizeof(WAVEHDR));
w->GOBUFFRec = ( w->GOBUFFRec + 1 ) % BUFFS;
break;
}
}
void CALLBACK HANDLESINK(HWAVEIN hwi, UINT umsg, DWORD dwi, DWORD hdr, DWORD dwparm)
{
int ctr;
long cValue;
unsigned int maxWave=0;
if (w->isEnding) return;
switch (umsg)
{
case MM_WOM_OPEN:
printf( "Sink Open.\n" );
w->playing = 1;
break;
case MM_WOM_DONE:
w->callback( (struct CNFADriver*)w, (short*)(w->WavBuffOut[w->GOBUFFPlay]).lpData, 0, w->buffer, 0 );
waveOutWrite( w->hMyWaveOut, &(w->WavBuffOut[w->GOBUFFPlay]),sizeof(WAVEHDR) );
w->GOBUFFPlay = ( w->GOBUFFPlay + 1 ) % BUFFS;
break;
}
}
static struct CNFADriverWin * InitWinCNFA( struct CNFADriverWin * r )
{
int i;
WAVEFORMATEX wfmt;
memset( &wfmt, 0, sizeof(wfmt) );
printf ("WFMT Size (debugging temp for TCC): %llu\n", sizeof(wfmt) );
printf( "WFMT: %d %d %d\n", r->channelsRec, r->spsRec, r->spsRec * r->channelsRec );
w = r;
wfmt.wFormatTag = WAVE_FORMAT_PCM;
wfmt.nChannels = r->channelsRec;
wfmt.nAvgBytesPerSec = r->spsRec * r->channelsRec;
wfmt.nBlockAlign = r->channelsRec * 2;
wfmt.nSamplesPerSec = r->spsRec;
wfmt.wBitsPerSample = 16;
wfmt.cbSize = 0;
long dwdeviceR, dwdeviceP;
dwdeviceR = r->sInputDev?atoi(r->sInputDev):WAVE_MAPPER;
dwdeviceP = r->sOutputDev?atoi(r->sOutputDev):WAVE_MAPPER;
if( r->channelsRec )
{
printf( "In Wave Devs: %d; WAVE_MAPPER: %d; Selected Input: %ld\n", waveInGetNumDevs(), WAVE_MAPPER, dwdeviceR );
int p = waveInOpen(&r->hMyWaveIn, dwdeviceR, &wfmt, (intptr_t)(&HANDLEMIC), 0, CALLBACK_FUNCTION);
if( p )
{
fprintf( stderr, "Error performing waveInOpen. Received code: %d\n", p );
}
printf( "waveInOpen: %d\n", p );
for ( i=0;i<BUFFS;i++)
{
memset( &(r->WavBuffIn[i]), 0, sizeof(r->WavBuffIn[i]) );
(r->WavBuffIn[i]).dwBufferLength = r->buffer*2*r->channelsRec;
(r->WavBuffIn[i]).dwLoops = 1;
(r->WavBuffIn[i]).lpData=(char*) malloc(r->buffer*r->channelsRec*2);
printf( "buffer gen size: %d: %p\n", r->buffer*r->channelsRec*2, (r->WavBuffIn[i]).lpData );
p = waveInPrepareHeader(r->hMyWaveIn,&(r->WavBuffIn[i]),sizeof(WAVEHDR));
printf( "WIPr: %d\n", p );
waveInAddBuffer(r->hMyWaveIn,&(r->WavBuffIn[i]),sizeof(WAVEHDR));
printf( "WIAr: %d\n", p );
}
p = waveInStart(r->hMyWaveIn);
if( p )
{
fprintf( stderr, "Error performing waveInStart. Received code %d\n", p );
}
}
wfmt.nChannels = r->channelsPlay;
wfmt.nAvgBytesPerSec = r->spsPlay * r->channelsPlay;
wfmt.nBlockAlign = r->channelsPlay * 2;
wfmt.nSamplesPerSec = r->spsPlay;
if( r->channelsPlay )
{
printf( "Out Wave Devs: %d; WAVE_MAPPER: %d; Selected Input: %ld\n", waveOutGetNumDevs(), WAVE_MAPPER, dwdeviceP );
int p = waveOutOpen( &r->hMyWaveOut, dwdeviceP, &wfmt, (intptr_t)(void*)(&HANDLESINK), (intptr_t)r, CALLBACK_FUNCTION);
if( p )
{
fprintf( stderr, "Error performing waveOutOpen. Received code: %d\n", p );
}
printf( "waveOutOpen: %d\n", p );
for ( i=0;i<BUFFS;i++)
{
memset( &(r->WavBuffOut[i]), 0, sizeof(r->WavBuffOut[i]) );
(r->WavBuffOut[i]).dwBufferLength = r->buffer*2*r->channelsPlay;
(r->WavBuffOut[i]).dwLoops = 1;
int size = r->buffer*r->channelsPlay*2;
char * buf = (r->WavBuffOut[i]).lpData=(char*) malloc(size);
memset( buf, 0, size );
p = waveOutPrepareHeader(r->hMyWaveOut,&(r->WavBuffOut[i]),sizeof(WAVEHDR));
waveOutWrite( r->hMyWaveOut, &(r->WavBuffOut[i]),sizeof(WAVEHDR));
}
}
return r;
}
void * InitCNFAWin( CNFACBType cb, const char * your_name, int reqSPSPlay, int reqSPSRec, int reqChannelsPlay, int reqChannelsRec, int sugBufferSize, const char * outputSelect, const char * inputSelect, void * opaque )
{
struct CNFADriverWin * r = (struct CNFADriverWin *)malloc( sizeof( struct CNFADriverWin ) );
memset( r, 0, sizeof(*r) );
r->CloseFn = CloseCNFAWin;
r->StateFn = CNFAStateWin;
r->callback = cb;
r->opaque = opaque;
r->spsPlay = reqSPSPlay;
r->spsRec = reqSPSRec;
r->channelsPlay = reqChannelsPlay;
r->channelsRec = reqChannelsRec;
r->buffer = sugBufferSize;
r->sInputDev = inputSelect?strdup(inputSelect):0;
r->sOutputDev = outputSelect?strdup(outputSelect):0;
r->recording = 0;
r->playing = 0;
r->isEnding = 0;
r->GOBUFFPlay = 0;
r->GOBUFFRec = 0;
return InitWinCNFA(r);
}
REGISTER_CNFA( WinCNFA, 10, "WIN", InitCNFAWin );

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 <>< Charles Lohr
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,34 @@
.PHONY: all shared shared-example wave_player clean
all: example wav_player
os_generic.h:
wget https://raw.githubusercontent.com/cntools/rawdraw/master/os_generic.h
LDFLAGS = -lasound -lpthread
ifeq "$(shell pkgconf --exists pulse && echo 'found' )" "found"
PULSE ?= "YES"
else
PULSE ?= "NO"
endif
ifeq "$(PULSE)" "YES"
LDFLAGS += -lpulse -DPULSEAUDIO
endif
example : example.c os_generic.h
$(CC) -o $@ $^ $(LDFLAGS) -lm
wav_player: os_generic.h
make -C wave_player PULSE=$(PULSE)
shared : os_generic.h
$(CC) CNFA.c -shared -fpic -o libCNFA.so -DCNFA_IMPLEMENTATION -DBUILD_DLL \
$(LDFLAGS)
shared-example : example.c shared
$(CC) -L. -Wl,-rpath=. -o example example.c -DUSE_SHARED -lm -lCNFA
clean :
rm -rf *.o *~ example libCNFA.so
make -C wave_player clean

View file

@ -0,0 +1,75 @@
# cnfa
CN's foundational audio drivers
See CNFA.h for more help and use info.
This toolset is 100% based around sound callbacks.
You get three functions:
```C
struct CNFADriver* CNFAInit(const char* driver_name,
const char* your_name,
CNFACBType cb,
int reqSPSPlay,
int reqSPSRec,
int reqChannelsPlay,
int reqChannelsRec,
int sugBufferSize,
const char* outputSelect,
const char* inputSelect,
void* opaque)
// Returns bitmask: 1 if mic recording, 2 if playback running, 3 if both running.
int CNFAState(struct CNFADriver* cnfaobject)
void CNFAClose(struct CNFADriver* cnfaobject)
```
Then it goes and calls a callback function, the `CNFACBType cb` parameter. This can feed you new frames, or you can pass frames back in.
`framesp` is the size of one channel of the output buffer in samples. If there are multiple channels they should be interleaved i.e. L:R:L:R...
`framerp` is the size of one channel of the input buffer in samples. Works the same way as the output buffer.
You can obtain the number of input/output channels from `sd->channelsRec` and `sd->channelsPlay` respectively.
```C
void Callback(struct CNFADriver* sd, short* out, short* in, int framesp, int framesr)
{
int i;
for( i = 0; i < framesr * sd->channelsRec; i++ )
short value = in[i];
for( i = 0; i < framesp * sd->channelsPlay; i++ )
out[i] = 0; //Send output frames.
}
```
There are two examples in this repository, [example.c](example.c) and [wave_player.c](wave_player/wav_player.c). Both of these show examples of using
CNFA to output sound. For use of CNFA for input see [colorchord](https://github.com/cnlohr/colorchord)
### Building .DLL and .SO files
If you would like to use CNFA in a project where using a DLL or SO file is more practical, you can easily build those files. The below steps are for the Clang & GGC compilers, but others like TCC should work fine as well, just have not been tested.
NOTE: In order for functions to be exported, you'll need to make sure `-DBUILD_DLL` is specified!
Parts of CNFA rely on [rawdraw](https://github.com/cntools/rawdraw), so make sure to clone this repo somewhere and insert the path to it in the `[RAWDRAW PATH]` space below:
**Don't forget to install all libraries' headers!** For Linux, at least these packages: `libasound2-dev`, `libpulse-dev`
Windows build:
```PS
& "C:\Program Files\LLVM\bin\clang.exe" CNFA.c -shared -o CNFA.dll -DWINDOWS -DCNFA_IMPLEMENTATION -DBUILD_DLL -D_CRT_SECURE_NO_WARNINGS -I"[RAWDRAW PATH]" -lmmdevapi -lavrt -lole32
```
Linux build:
```Bash
make shared-example
```
This will build the shared library as `libCNFA.so` and will build the example
project to link against it. You can run the example project with `./example`.
The makefile will try and copy the `os_generic.h` header from
[rawdraw](https://github.com/cntools/rawdraw)
automatically using `wget`.
The command to build simply build the shared library is:
```Bash
gcc CNFA.c -shared -fpic -o CNFA.so -DCNFA_IMPLEMENTATION -DBUILD_DLL -I"[RAWDRAW PATH]" -lasound -lpulse -lpthread
```

View file

@ -0,0 +1,65 @@
#include <stdio.h>
#include <math.h>
// If using the shared library, don't define CNFA_IMPLEMENTATION
// (it's already in the library).
#ifndef USE_SHARED
#define CNFA_IMPLEMENTATION
#endif
#include "CNFA.h"
#define RUNTIME 500000
double omega = 0;
int totalframesr = 0;
int totalframesp = 0;
void Callback( struct CNFADriver * sd, short * out, short * in, int framesp, int framesr )
{
int i;
totalframesr += framesr;
totalframesp += framesp;
int channels = sd->channelsPlay;
for( i = 0; i < framesp; i++ )
{
// Shift phase, so we run at 440 Hz (A4)
omega += ( 3.14159 * 2 * 440. ) / sd->spsPlay;
// Make the 440 Hz tone at 10% volume and convert to short.
short value = sin( omega ) * 0.1 * 32767;
int c;
for( c = 0; c < channels; c++ )
{
*(out++) = value;
}
}
}
struct CNFADriver * cnfa;
int main( int argc, char ** argv )
{
cnfa = CNFAInit(
//"PULSE",
"ALSA", //You can select a plaback driver, or use 0 for default.
//0, //default
"cnfa_example", Callback,
48000, //Requested samplerate for playback
48000, //Requested samplerate for recording
2, //Number of playback channels.
2, //Number of record channels.
1024, //Buffer size in frames.
0, //Could be a string, for the selected input device - but 0 means default.
0, //Could be a string, for the selected output device - but 0 means default.
0 // 'opaque' value if the driver wanted it.
);
sleep( RUNTIME );
printf( "Received %d (%d per sec) frames\nSent %d (%d per sec) frames\n", totalframesr, totalframesr/RUNTIME, totalframesp, totalframesp/RUNTIME );
}

View file

@ -0,0 +1,27 @@
.PHONY: clean
C_SRCS = wav_player.c
OUT := wav_player
CFLAGS = -O2 -g
LDFLAGS = -lasound -lpthread -lm
CC ?= gcc -std=c99
ifeq "$(shell pkgconf --exists pulse && echo 'found' )" "found"
PULSE ?= "YES"
else
PULSE ?= "NO"
endif
ifeq "$(PULSE)" "YES"
LDFLAGS += -lpulse
endif
OBJS := $(C_SRCS:.c=.o)
$(OUT): $(OBJS)
$(CC) $(CFLAGS) $(LDFLAGS) $(OBJS) -o $(OUT)
clean:
rm $(OBJS) $(OUT)
.c.o:
$(CC) $(CFLAGS) -c $< -o $@

View file

@ -0,0 +1,117 @@
/*
* wavDefs.h Samuel Ellicott - 9-3-13
* defines basic features of PCM encoded wav files
*/
#ifndef _WAV_DEFS_H_
#define _WAV_DEFS_H_
#include <stdint.h>
//#define ABSOLUTE 1
#ifdef ABSOLUTE
//absolute offsets PCM audacity files only
// name offset description
//RIFF chunk descriptor
#define CHUNK_ID 0 //start of the riff chunk
#define CHUNK_SIZE 4 //size of the file minus the first 2 entries
#define FORMAT 8 //describes the format aka. WAVE
//fmt sub-chunk descriptor
#define FMT_CHUNK_ID 12 //start of the fmt sub chunk
#define FMT_CHUNK_SIZE 16 //length of the rest of the sub-chunk. for PCM should be 16
#define AUDIO_FORMAT 20 //audio format - for PCM should be 1 (unsigned char)
#define NUM_CHANNELS 22 //the number of channels 1 or 2 (unsigned char)
#define SAMPLE_RATE 24 //sample rate 8000, 44100, or 48000 typical in hz
#define BIT_RATE 28 //bit rate - (SampleRate * NumChannels * BitsPerSample)/8
#define BLOCK_ALIGN 32 //NumChannels * BitsPerSample/8 (unsigned char)
//1 - 8 bit mono, 2 - 8 bit stereo/16 bit mono, 4 - 16 bit stereo
#define BITS_PER_SAMPLE 34 //bits per channel 8 or 16 (unsigned char)
//fmt sub-chunk descriptor
#define CHUNK_ID_1 12 //start of the fmt sub chunk
#define CHUNK_SIZE_1 16 //length of the rest of the sub-chunk. for PCM should be 1
//info data sub-chunk descriptor
//if Wav_Data.is_info == 1 than this is info chunk
//if Wav_Data.is_info == 0 than this is data chunk
#define _CHUNK_ID 36 //start of data sub chunk
#define _CHUNK_SIZE 40 //chunk size of sub chunk
#define _CHUNK_DATA 44 //data in chunk
#else
//general chunk descriptor
#define CHUNK_ID 0 //start of the riff chunk
#define CHUNK_SIZE 4 //size of the file minus the first 2 entries
#define FORMAT 8 //describes the format for RIFF and LIST chunks
#define CHUNK_DATA 8 //start of data for other chunks
#define CHUNK_ID_LEN 5 //length of chunk ID string (including null char)
//fmt sub-chunk descriptor
#define FMT_CHUNK_ID 0 //start of the fmt sub chunk
#define FMT_CHUNK_SIZE 4 //length of the rest of the sub-chunk. for PCM should be 16
#define AUDIO_FORMAT 8 //audio format - for PCM should be 1 (unsigned char)
#define NUM_CHANNELS 10 //the number of channels 1 or 2 (unsigned char)
#define SAMPLE_RATE 12 //sample rate 8000, 44100, or 48000 typical in hz
#define BIT_RATE 16 //bit rate - (SampleRate * NumChannels * BitsPerSample)/8
#define BLOCK_ALIGN 20 //NumChannels * BitsPerSample/8 (unsigned char)
//1 - 8 bit mono, 2 - 8 bit stereo/16 bit mono, 4 - 16 bit stereo
#define BITS_PER_SAMPLE 22 //bits per channel 8 or 16 (unsigned char)
#endif //ABSOLUTE
#define MAX_TAG_SIZE 100 //defines the maximum number of characters to be allocated for any info string
/*
* data on the optional INFO data chunk contains pointers to the following info:
* title
* author
* genre
*/
typedef struct WaveInfoChunk{
uint8_t is_info; //1 for info data, 0 for no info data
uint32_t info_offset; //the file offset for the info chunk
uint32_t info_len; //length of the info chunk
//begin heap pointers
char *title;
char *artist;
char *genre;
char *creation_date;
} WaveInfoChunk;
/*
* data structure containing the data neccesarry for PCM audio playback
*/
typedef struct WaveFmtChunk{
uint32_t fmt_len; //fmt size: 16 for pcm
uint16_t audio_format; //1 = PCM
uint16_t num_channels; //1 for mono, 2 for stereo
uint32_t sample_rate; //44100 (CD), 48000 (DAT)
uint32_t byte_rate; //SampleRate * NumChannels * BitsPerSample/8
uint16_t block_align; //1 - 8 bit mono
//2 - 8 bit stereo/16 bit mono
//4 - 16 bit stereo
uint16_t bits_per_sample; //8 or 16
uint8_t bytes_per_sample;
} WaveFmtChunk;
typedef struct WaveDataChunk{
uint32_t data_offset; //the file offset for data chunk
uint32_t data_size; //data size in bytes
uint32_t current_offset; //current offset in file in bytes
uint16_t num_samples; //number of samples
uint16_t samples_left; //number of samples left to read
} WaveDataChunk;
/*
* struct all the pertinent information for the wav file
*/
typedef struct WaveHeaderChunk{
struct WaveFmtChunk fmt; //the wave format chunk
struct WaveInfoChunk info; //the info metadata chunk
struct WaveDataChunk data;
} WaveHeaderChunk;
#endif //_WAV_DEFS_H_

View file

@ -0,0 +1,389 @@
/*
* Sam Ellicott - 09-06-22
* CNFA demo wave file player
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "wavDefs.h"
// wave file player function prototypes
int loadHeader(FILE *file, WaveHeaderChunk *hdr);
int loadInfo(FILE *file, WaveHeaderChunk *hdr);
int printInfo(FILE *file);
int readData(FILE *file, WaveHeaderChunk *hdr, void* buff, int buff_len);
void freeData(WaveHeaderChunk *hdr);
/* ------------------------------------------ Main Application Code ------------------------------*/
#define BUFF_SIZE 2048
// If using the shared library, don't define CNFA_IMPLEMENTATION
// (it's already in the library).
#ifndef USE_SHARED
#define CNFA_IMPLEMENTATION
#endif
#include "../CNFA.h"
int totalframesr = 0;
int totalframesp = 0;
FILE* wav_file;
WaveHeaderChunk hdr;
struct CNFADriver * cnfa;
short buff[BUFF_SIZE];
int is_done;
void Callback( struct CNFADriver * sd, short * out, short * in, int framesp, int framesr )
{
int* is_done_ptr = (int*) sd->opaque;
const int output_channels = sd->channelsPlay;
const int file_channels = hdr.fmt.num_channels;
const int output_buff_sz = framesp * output_channels;
int br = 0;
// if we have already ended the file, then clear the buffer and exit function
if(*is_done_ptr) {
memset(out, 0, sizeof(short) * output_buff_sz);
return;
}
totalframesr += framesr;
totalframesp += framesp;
if (output_channels == file_channels) {
int read_buff_sz = framesp * sd->channelsPlay;
br = readData(wav_file, &hdr, out, read_buff_sz);
}
else if (output_channels > file_channels) {
int read_buff_sz = framesp;
// exit loop if we are done filling the output buffer or we are at the end of file
int samples_remaining = read_buff_sz;
while (samples_remaining > 0 && br >= 0) {
int read_sz = (samples_remaining > BUFF_SIZE) ? BUFF_SIZE : samples_remaining;
br = readData(wav_file, &hdr, buff, read_sz);
// duplicate data on left and right channels
for (int i = 0; i < read_buff_sz; ++i){
out[2*i] = buff[i];
out[2*i+1] = buff[i];
}
samples_remaining -= br;
}
}
else {
printf("what are you doing? mono sound output?\n");
}
// end of file
if (br < 0) {
printf("End of wave file: setting flag\n");
*is_done_ptr = 1;
}
}
int main (int nargs, char** args) {
const char* filename;
// if there is a file given on the command line play it.
if(nargs >= 2) {
filename = args[1];
}
else {
printf("\nError, no input file\nUseage %s <wave file>\n", args[0]);
return 1;
}
wav_file = fopen(filename, "r");
printInfo(wav_file);
printf("\n\n");
printf("loading file\n");
loadHeader(wav_file, &hdr);
printf("playing file\n");
is_done = 0;
cnfa = CNFAInit(
NULL, // String, for the driver "PULSE", "WASAPI" (output only) - NULL means default.
"cnfa_example", // Name of program to audio driver
Callback, // CNFA callback function handle
hdr.fmt.sample_rate, // Requested samplerate for playback
hdr.fmt.sample_rate, // Requested samplerate for record
2, // Number of playback channels.
2, // Number of record channels.
1024, // Buffer size in frames.
NULL, // String, for the selected input device - NULL means default.
NULL, // String, for the selected output device - NULL means default.
&is_done // pass an integer as an "opaque" object so that CNFA can close
);
int runtime = 0;
const char* spin_glyph = "-\\|/";
const char* glyph = spin_glyph;
int i = 0;
while (!is_done){
sleep(1);
++runtime;
printf("\r %c ", *glyph++);
fflush(stdout);
if (!*glyph) {
glyph = spin_glyph;
}
}
CNFAClose(cnfa);
fclose(wav_file);
printf( "Received %d (%d per sec) frames\nSent %d (%d per sec) frames\n",
totalframesr, totalframesr/runtime, // recorded samples, recorded samples/sec
totalframesp, totalframesp/runtime ); // outputted samples, outputted samples/sec
return 0;
}
/* ------------------------------------------ Wave File Player Code ------------------------------*/
int printInfo(FILE *file){
WaveHeaderChunk wav_data;
if(loadHeader(file, &wav_data)!=0){
printf("file invalid\n\r");
return 1;
}
//print file data
printf("\n");
printf("Audio Format: %i \n", wav_data.fmt.audio_format);
printf("Channels: %u \n", wav_data.fmt.num_channels);
printf("Sample Rate: %lu \n", wav_data.fmt.sample_rate);
printf("Block Alignment (bytes): %u \n", wav_data.fmt.block_align);
printf("Bits-per-Sample: %u \n", wav_data.fmt.bits_per_sample);
if(loadInfo(file, &wav_data)==0){//if there is data show it
printf("\n");
printf("Track name: %s \n\r", wav_data.info.title);
printf("Artist: %s \n\r", wav_data.info.artist);
printf("Genre: %s \n\r", wav_data.info.genre);
printf("Creation date: %s \n\r", wav_data.info.creation_date);
}
freeData(&wav_data);
return 0;
}
int loadHeader(FILE *file, WaveHeaderChunk *hdr){
char chunk_id[CHUNK_ID_LEN];
uint32_t chunk_offset=0;//keeps track of position in file - referenced to the chunk id
uint32_t chunk_len=0;//the length of the current chunk
uint32_t br;
//check if the file is valid first
if(file==NULL){
printf("Could not open file \n");
return 1;
}
// check that the pointer is valid
if (!hdr) {
return 2;
}
// clear pointers
hdr->info.creation_date = NULL;
hdr->info.genre = NULL;
hdr->info.artist = NULL;
hdr->info.title = NULL;
//look for RIFF/WAVE file header
fseek(file, CHUNK_ID, SEEK_SET);
fgets(chunk_id, CHUNK_ID_LEN, file);
if(strncmp(chunk_id, "RIFF", 4)!=0){
printf("File is not a wav file\n");
return 1;
}
fseek(file, FORMAT, SEEK_SET);
fgets(chunk_id, CHUNK_ID_LEN, file);
if(strncmp(chunk_id, "WAVE", 4)!=0){
printf("File is not a wav file \n");
return 1;
}
chunk_offset+=CHUNK_DATA+4;//add the file offset of the begining of the first chunk
//must be WAVE look for chunks
do{
//get the chunk id
fseek(file, chunk_offset+CHUNK_ID, SEEK_SET);
fgets(chunk_id, CHUNK_ID_LEN, file);
//get the chunk length
fseek(file, chunk_offset+CHUNK_SIZE, SEEK_SET);
br = fread(&chunk_len, sizeof(uint32_t), 1, file);//get length
//check for fmt chunk
if(strncmp(chunk_id, "fmt ", 4)==0){//if format section
//get the format section of the file
fseek(file, chunk_offset+AUDIO_FORMAT, SEEK_SET);
br = fread(&hdr->fmt.audio_format, sizeof(uint16_t), 1, file);
//number of channels
fseek(file, chunk_offset+NUM_CHANNELS, SEEK_SET);
br = fread(&hdr->fmt.num_channels, sizeof(uint16_t), 1, file);
//sample rate
fseek(file, chunk_offset+SAMPLE_RATE, SEEK_SET);
br = fread(&hdr->fmt.sample_rate, sizeof(uint32_t), 1, file);
//bits per channel tells if mono, stereo, or hifi stereo
fseek(file, chunk_offset+BLOCK_ALIGN, SEEK_SET);
br = fread(&hdr->fmt.block_align, sizeof(uint16_t), 1, file);
//bits per channel 8 or 16
fseek(file, chunk_offset+BITS_PER_SAMPLE, SEEK_SET);
br = fread(&hdr->fmt.bits_per_sample, sizeof(uint16_t), 1, file);
hdr->fmt.bytes_per_sample=hdr->fmt.bits_per_sample/8;
}
else if(strncmp(chunk_id, "data", 4)==0){//if chunk is of data type
// grab data chunk location and size
hdr->data.data_offset = chunk_offset;
hdr->data.data_size = chunk_len;
hdr->data.num_samples = (uint16_t) chunk_len/hdr->fmt.bytes_per_sample;
hdr->data.samples_left = hdr->data.num_samples;
hdr->data.current_offset = chunk_offset+CHUNK_DATA;
}
else if(strncmp(chunk_id, "LIST", 4)==0){//if chunk is of LIST type
//check if it is of info type
fseek(file, chunk_offset+CHUNK_DATA, SEEK_SET);
fgets(chunk_id, 5, file);
if(strncmp(chunk_id, "INFO", 4)==0){
// grab info location and size
hdr->info.info_offset = chunk_offset+4;
hdr->info.info_len = chunk_len;
hdr->info.is_info = 1;
}
else{
// no file info (artist, genre, etc)
hdr->info.is_info = 0;
}
}
else if(strncmp(chunk_id, "data", 4)!=0){
//get length of chunk
fseek(file, chunk_offset+CHUNK_SIZE, SEEK_SET);
br = fread(&chunk_len, 2, 1, file);//get length
}
//done reading skip chunk
chunk_offset=chunk_offset+chunk_len+CHUNK_DATA;
fseek(file, chunk_offset, SEEK_SET);
} while(fgets(chunk_id, 5, file)!=NULL);//loop until the end of the file is reached
return 0;
}
/*
* reads the info chunk if available and puts data into the WaveHeader structure provided
*/
int loadInfo(FILE *file, WaveHeaderChunk *hdr){
char chunk_id[CHUNK_ID_LEN];
uint32_t chunk_offset=hdr->info.info_offset+CHUNK_DATA;//current file position
uint32_t chunk_len=0;//the length of the current chunk
uint32_t tag_len=0;
uint32_t br;
if(hdr->info.is_info==0){//if no data
printf("No artist information is available for this wav file \n");
return 1;//no data
}
fseek(file, chunk_offset, SEEK_SET);//go to chunk data
while(chunk_offset-4 - hdr->info.info_offset < hdr->info.info_len){//while in info chunk
//get the chunk id
fseek(file, chunk_offset+CHUNK_ID, SEEK_SET);
fgets(chunk_id, 5, file);
//get the chunk length
fseek(file, chunk_offset+CHUNK_SIZE, SEEK_SET);
br = fread(&chunk_len, sizeof(uint32_t), 1, file);//get length
//make sure the length of the tag isn't to big
if(chunk_len > MAX_TAG_SIZE){//if too big
tag_len=MAX_TAG_SIZE;
}
else{
tag_len=chunk_len;
}
//go to tag information
fseek(file, chunk_offset+CHUNK_DATA, SEEK_SET);
if(strncmp(chunk_id, "INAM", 4)==0){ // if is name chunk
hdr->info.title=malloc(tag_len+1); // allocate memory for tag
fgets(hdr->info.title, tag_len, file); // read tag
}
else if(strncmp(chunk_id, "IART", 4)==0){ // if is artist chunk
hdr->info.artist=malloc(tag_len+1); // allocate memory for tag
fgets(hdr->info.artist, tag_len, file); // read tag
}
else if(strncmp(chunk_id, "IGNR", 4)==0){ // if is genre chunk
hdr->info.genre=malloc(tag_len+1); // allocate memory for tag
fgets(hdr->info.genre, tag_len, file); // read tag
}
else if(strncmp(chunk_id, "ICRD", 4)==0){ // if is creation date chunk
hdr->info.creation_date=malloc(tag_len+1); // allocate memory for tag
fgets(hdr->info.creation_date, tag_len, file); // read tag
}
//done reading skip chunk
chunk_offset=chunk_offset+chunk_len+CHUNK_DATA;
fseek(file, chunk_offset, SEEK_SET);
}
return 0;
}
int readData(FILE *file, WaveHeaderChunk *hdr, void* buff, int buff_len){
uint32_t br;
int bytes_to_read = hdr->fmt.bytes_per_sample*buff_len;
if(!hdr) {
printf("Error: No valid header\n");
return -1;
}
fseek(file, hdr->data.current_offset, SEEK_SET);
br = fread(buff, hdr->fmt.bytes_per_sample, buff_len, file);
int bytes_read = hdr->fmt.bytes_per_sample*br;
//printf("samples read: %u\n", br);
hdr->data.samples_left -= br;
hdr->data.current_offset += bytes_read;
if(feof(file)){
br = -1;
}
return br;
}
void freeData(WaveHeaderChunk *hdr) {
//free the info tag heap data
if(hdr->info.title){
free(hdr->info.title);
hdr->info.title=NULL;
}
if(hdr->info.artist){
free(hdr->info.artist);
hdr->info.artist=NULL;
}
if(hdr->info.genre){
free(hdr->info.genre);
hdr->info.genre=NULL;
}
if(hdr->info.creation_date){
free(hdr->info.creation_date);
hdr->info.creation_date=NULL;
}
}

View file

@ -0,0 +1,41 @@
name: rawdraw
run-name: ${{ github.actor }} Executing
on:
push:
branches:
- '**'
pull_request:
branches:
- '**'
jobs:
Explore-GitHub-Actions:
runs-on: ubuntu-latest
steps:
- run: echo "Triggered by ${{ github.event_name }} on ${{ runner.os }}. Branch Ref ${{ github.ref }}."
- name: Install dependencies.
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends
build-essential
openjdk-11-jdk-headless
unzip
wget
zip
git
vim
gettext-base
- name: Check out respository code
uses: actions/checkout@v3
with:
submodules: recursive
- name: Building rawdraw_sf.h
run: |
rm rawdraw_sf.h
make rawdraw_sf.h
#- name: Commit rawdraw_sf.h
# run: |
# git_hash=$(git rev-parse --short "$GITHUB_SHA")
# git commit -m "Updating rawdraw_sf.h to ${git_hash}" rawdraw_sf.h
- uses: actions/upload-artifact@v3
with:
name: rawdraw_sf
path: rawdraw_sf.h

17
app/src/nativecode/rawdraw/.gitignore vendored Normal file
View file

@ -0,0 +1,17 @@
.vs/*
Debug/*
Release/*
TextTool/FontBackup.c
TextTool/text
*.exe
ogltest
rawdraw
rawdraw_ogl
simple
examples/fontsize
examples/fontsize_ogl
tools/binary_to_buffer
tools/single_file_creator
tools/rawdraw_http_page.h
rawdraw_sf.hf

View file

@ -0,0 +1,28 @@
//Include this file to get all of rawdraw. You usually will not
//want to include this in your build, but instead, #include "CNFG.h"
//after #define CNFG_IMPLEMENTATION in one of your C files.
// Defined here for universal definition
int CNFGLastCharacter = 0;
int CNFGLastScancode = 0;
#if defined( CNFGHTTP )
#include "CNFGHTTP.c"
#elif defined( __wasm__ )
#include "CNFGWASMDriver.c"
#elif defined(WINDOWS) || defined(WIN32) || defined(WIN64) || defined(_WIN32) || defined(_WIN64)
#include "CNFGWinDriver.c"
#elif defined( EGL_LEAN_AND_MEAN )
#include "CNFGEGLLeanAndMean.c"
#elif defined( __android__ ) || defined( ANDROID )
#include "CNFGEGLDriver.c"
#else
#include "CNFGXDriver.c"
#endif
#include "CNFGFunctions.c"
#ifdef CNFG3D
#include "CNFG3D.c"
#endif

View file

@ -0,0 +1,545 @@
// Copyright 2010-2021 <>< CNLohr, et. al. (Several other authors, many but not all mentioned)
// Licensed under the MIT/x11 or NewBSD License you choose.
//
// CN Foundational Graphics Main Header File. This is the main header you
// should include. See README.md for more details.
#ifndef _CNFG_H
#define _CNFG_H
#ifdef __cplusplus
extern "C" {
#endif
/* Rawdraw flags:
CNFG3D -> Enable the weird 3D functionality that rawdraw has to allow you to
write apps which emit basic rawdraw primitives but look 3D!
CNFG_USE_DOUBLE_FUNCTIONS -> Use double-precision floating point for CNFG3D.
CNFGOGL -> Use an OpenGL Backend for all rawdraw functionality.
->Caveat->If using CNFG_HAS_XSHAPE, then, we do something realy wacky.
CNFGRASTERIZER -> Software-rasterize the rawdraw calls, and, use
CNFGUpdateScreenWithBitmap to send video to webpage.
CNFGCONTEXTONLY -> Don't add any drawing functions, only opening a window to
get an OpenGL context.
CNFG_IMPLEMENTATION -> #define this and it will make _this_ the file where the cnfg
functions actually live.
Usually tested combinations:
* TCC On Windows and X11 (Linux) with:
- CNFGOGL on or CNFGOGL off. If CNFGOGL is off you can use
CNFG_WINDOWS_DISABLE_BATCH to disable all batching.
-or-
- CNFGRASTERIZER
NOTE: Sometimes you can also use CNFGOGL + CNFGRASTERIZER
* WASM driver supports both: CNFGRASTERIZER and without CNFGRASTERIZER (Recommended turn rasterizer off)
* ANDROID (But this automatically sets CNFGRASTERIZER OFF and CNFGOGL ON)
Unusual compiler flags:
* CNFGHTTP - Enable the HTTP server-version of rawdraw, where it renders to a website.
* CNFGHTTPSERVERONLY - if you want to use the HTTP server w/o rawdraw. You will need to implement:
- CloseEvent, HTTPCustomCallback, HTTPCustomStart, NewWebSocket, WebSocketData, WebSocketTick
* CNFG_DISABLE_HTTP_FILES - disable the HTTP file server.
*/
#include <stdint.h>
//Some per-platform logic.
#if defined( ANDROID ) || defined( __android__ )
#define CNFGOGL
#endif
#if ( defined( CNFGOGL ) || defined( __wasm__ ) ) && !defined(CNFG_HAS_XSHAPE)
#define CNFG_BATCH 8192 //131,072 bytes.
#if defined( ANDROID ) || defined( __android__ ) || defined( __wasm__ ) || defined( EGL_LEAN_AND_MEAN )
#define CNFGEWGL //EGL or WebGL
#else
#define CNFGDESKTOPGL
#endif
#endif
typedef struct {
short x, y;
} RDPoint;
extern int CNFGPenX, CNFGPenY;
extern uint32_t CNFGBGColor;
extern uint32_t CNFGLastColor;
extern uint32_t CNFGDialogColor; //Only used for DrawBox
//Draws text at CNFGPenX, CNFGPenY, with scale of `scale`.
void CNFGDrawText( const char * text, short scale );
//Determine how large a given test would be to draw.
void CNFGGetTextExtents( const char * text, int * w, int * h, int textsize );
//Draws a box, outline as whatever the last CNFGColor was set to but also draws
//a rectangle as a background as whatever CNFGDialogColor is set to.
void CNFGDrawBox( short x1, short y1, short x2, short y2 );
//To be provided by driver. Rawdraw uses colors in the format 0xRRGGBBAA
//Note that some backends do not support alpha of any kind.
//Some platforms also support alpha blending. So, be sure to set alpha to 0xFF
uint32_t CNFGColor( uint32_t RGBA );
//This both updates the screen, and flips, all as a single operation.
void CNFGUpdateScreenWithBitmap( uint32_t * data, int w, int h );
//This is only supported on a FEW architectures, but allows arbitrary
//image blitting. Note that the alpha channel behavior is different
//on different systems.
void CNFGBlitImage( uint32_t * data, int x, int y, int w, int h );
// Only supported with CNFGOGL
#ifdef CNFGOGL
void CNFGDeleteTex( unsigned int tex );
unsigned int CNFGTexImage( uint32_t *data, int w, int h );
void CNFGBlitTex( unsigned int tex, int x, int y, int w, int h );
#endif
void CNFGTackPixel( short x1, short y1 );
void CNFGTackSegment( short x1, short y1, short x2, short y2 );
void CNFGTackRectangle( short x1, short y1, short x2, short y2 );
void CNFGTackPoly( RDPoint * points, int verts );
void CNFGClearFrame();
void CNFGSwapBuffers();
void CNFGGetDimensions( short * x, short * y );
//This will setup a window. Note that w and h have special meaning. On Windows
//and X11, for instance if you set w and h to be negative, then rawdraw will not
//show the window to the user. This is useful if you just need it for some
//off-screen-rendering purpose.
//
//Return value of 0 indicates success. Nonzero indicates error.
int CNFGSetup( const char * WindowName, int w, int h );
void CNFGSetupFullscreen( const char * WindowName, int screen_number );
int CNFGHandleInput();
//You must provide:
void HandleKey( int keycode, int bDown );
void HandleButton( int x, int y, int button, int bDown );
void HandleMotion( int x, int y, int mask );
int HandleDestroy(); // Return nonzero if you want to cancel destroy.
#ifdef ANDROID_WANT_WINDOW_TERMINATION
void HandleWindowTermination();
#endif
// Guaranteed to be for the current key inside HandleKey for drivers that support it
// If drivers don't support it, it is 0
extern int CNFGLastCharacter;
extern int CNFGLastScancode;
//Internal function for resizing rasterizer for rasterizer-mode.
void CNFGInternalResize( short x, short y ); //don't call this.
//Not available on all systems. Use The OGL portion with care.
#ifdef CNFGOGL
void CNFGSetVSync( int vson );
void * CNFGGetExtension( const char * extname );
#endif
//Also not available on all systems. Transparency.
void CNFGPrepareForTransparency();
void CNFGDrawToTransparencyMode( int transp );
void CNFGClearTransparencyLevel();
//Only available on systems that support it.
void CNFGSetLineWidth( short width );
void CNFGChangeWindowTitle( const char * windowtitle );
void CNFGSetWindowIconData( int w, int h, uint32_t * data );
int CNFGSetupWMClass( const char * WindowName, int w, int h , char * wm_res_name_ , char * wm_res_class_ );
// Mouse related functions on systems that support it.
// enum just in case we want more cursor shapes
typedef enum {
CNFG_CURSOR_HIDDEN, CNFG_CURSOR_ARROW, CNFG_CURSOR_LAST
} CNFGCursorShape;
// This may put a motion event into the queue, which will call HandleMotion
void CNFGSetMousePosition( int x, int y );
void CNFGConfineMouse( int confined );
void CNFGSetCursor( CNFGCursorShape shape );
//If you're using a batching renderer, for instance on Android or an OpenGL
//You will need to call this function inbetewen swtiching properties of drawing. This is usually
//only needed if you calling OpenGL / OGLES functions directly and outside of CNFG.
//
//Note that these are the functions that are used on the backends which support this
//sort of thing.
#ifdef CNFG_BATCH
//If you are not using the CNFGOGL driver, you will need to define these in your driver.
void CNFGEmitBackendTriangles( const float * vertices, const uint32_t * colors, int num_vertices );
void CNFGBlitImage( uint32_t * data, int x, int y, int w, int h );
//These need to be defined for the specific driver.
void CNFGClearFrame();
void CNFGSwapBuffers();
void CNFGFlushRender(); //Emit any geometry (lines, squares, polys) which are slated to be rendered.
void CNFGInternalResize( short x, short y ); //Driver calls this after resize happens.
void CNFGSetupBatchInternal(); //Driver calls this after setup is complete.
//Useful function for emitting a non-axis-aligned quad.
void CNFGEmitQuad( float cx0, float cy0, float cx1, float cy1, float cx2, float cy2, float cx3, float cy3 );
extern int CNFGVertPlace;
extern float CNFGVertDataV[CNFG_BATCH*3];
extern uint32_t CNFGVertDataC[CNFG_BATCH];
#endif
#define CNFG_KEY_FOCUS 0xf000
#if defined(WINDOWS) || defined(WIN32) || defined(WIN64) || defined(_WIN32) || defined(_WIN64)
#define CNFG_KEY_BACKSPACE 0x08
#define CNFG_KEY_TAB 0x09
#define CNFG_KEY_CLEAR 0x0C
#define CNFG_KEY_ENTER 0x0D
#define CNFG_KEY_SHIFT 0x10
#define CNFG_KEY_CTRL 0x11
#define CNFG_KEY_ALT 0x12
#define CNFG_KEY_PAUSE 0x13
#define CNFG_KEY_CAPS_LOCK 0x14
#define CNFG_KEY_ESCAPE 0x1B
#define CNFG_KEY_SPACE 0x20
#define CNFG_KEY_PAGE_UP 0x21
#define CNFG_KEY_PAGE_DOWN 0x22
#define CNFG_KEY_END 0x23
#define CNFG_KEY_HOME 0x24
#define CNFG_KEY_LEFT_ARROW 0x25
#define CNFG_KEY_TOP_ARROW 0x26
#define CNFG_KEY_RIGHT_ARROW 0x27
#define CNFG_KEY_BOTTOM_ARROW 0x28
#define CNFG_KEY_SELECT 0x29
#define CNFG_KEY_PRINT 0x2A
#define CNFG_KEY_EXECUTE 0x2B
#define CNFG_KEY_PRINT_SCREEN 0x2C
#define CNFG_KEY_INSERT 0x2D
#define CNFG_KEY_DELETE 0x2E
#define CNFG_KEY_HELP 0x2F
#define CNFG_KEY_LEFT_SUPER 0x5B
#define CNFG_KEY_RIGHT_SUPER 0x5C
#define CNFG_KEY_NUM_0 0x60
#define CNFG_KEY_NUM_1 0x61
#define CNFG_KEY_NUM_2 0x62
#define CNFG_KEY_NUM_3 0x63
#define CNFG_KEY_NUM_4 0x64
#define CNFG_KEY_NUM_5 0x65
#define CNFG_KEY_NUM_6 0x66
#define CNFG_KEY_NUM_7 0x67
#define CNFG_KEY_NUM_8 0x68
#define CNFG_KEY_NUM_9 0x69
#define CNFG_KEY_NUM_MULTIPLY 0x6A
#define CNFG_KEY_NUM_ADD 0x6B
#define CNFG_KEY_NUM_SEPARATOR 0x6C
#define CNFG_KEY_NUM_SUBTRACT 0x6D
#define CNFG_KEY_NUM_DECIMAL 0x6E
#define CNFG_KEY_NUM_DIVIDE 0x6F
#define CNFG_KEY_F1 0x70
#define CNFG_KEY_F2 0x71
#define CNFG_KEY_F3 0x72
#define CNFG_KEY_F4 0x73
#define CNFG_KEY_F5 0x74
#define CNFG_KEY_F6 0x75
#define CNFG_KEY_F7 0x76
#define CNFG_KEY_F8 0x77
#define CNFG_KEY_F9 0x78
#define CNFG_KEY_F10 0x79
#define CNFG_KEY_F11 0x7A
#define CNFG_KEY_F12 0x7B
#define CNFG_KEY_F13 0x7C
#define CNFG_KEY_F14 0x7D
#define CNFG_KEY_F15 0x7E
#define CNFG_KEY_F16 0x7F
#define CNFG_KEY_F17 0x80
#define CNFG_KEY_F18 0x81
#define CNFG_KEY_F19 0x82
#define CNFG_KEY_F20 0x83
#define CNFG_KEY_F21 0x84
#define CNFG_KEY_F22 0x85
#define CNFG_KEY_F23 0x86
#define CNFG_KEY_F24 0x87
#define CNFG_KEY_NUM_LOCK 0x90
#define CNFG_KEY_SCROLL_LOCK 0x91
#define CNFG_KEY_LEFT_SHIFT 0xA0
#define CNFG_KEY_RIGHT_SHIFT 0xA1
#define CNFG_KEY_LEFT_CONTROL 0xA2
#define CNFG_KEY_RIGHT_CONTROL 0xA3
#define CNFG_KEY_LEFT_ALT 0xA4
#define CNFG_KEY_RIGHT_ALT 0xA5
#elif defined( EGL_LEAN_AND_MEAN ) // doesn't have any keys
#elif defined( __android__ ) || defined( ANDROID ) // ^
#elif defined( __wasm__ )
#define CNFG_KEY_BACKSPACE 8
#define CNFG_KEY_TAB 9
#define CNFG_KEY_CLEAR 12
#define CNFG_KEY_ENTER 13
#define CNFG_KEY_SHIFT 16
#define CNFG_KEY_CTRL 17
#define CNFG_KEY_ALT 18
#define CNFG_KEY_PAUSE 19
#define CNFG_KEY_CAPS_LOCK 20
#define CNFG_KEY_ESCAPE 27
#define CNFG_KEY_SPACE 32
#define CNFG_KEY_PAGE_UP 33
#define CNFG_KEY_PAGE_DOWN 34
#define CNFG_KEY_END 35
#define CNFG_KEY_HOME 36
#define CNFG_KEY_LEFT_ARROW 37
#define CNFG_KEY_TOP_ARROW 38
#define CNFG_KEY_RIGHT_ARROW 39
#define CNFG_KEY_BOTTOM_ARROW 40
#define CNFG_KEY_SELECT 41
#define CNFG_KEY_PRINT 42
#define CNFG_KEY_EXECUTE 43
#define CNFG_KEY_PRINT_SCREEN 44
#define CNFG_KEY_INSERT 45
#define CNFG_KEY_DELETE 46
#define CNFG_KEY_HELP 47
#define CNFG_KEY_LEFT_SUPER 91
#define CNFG_KEY_RIGHT_SUPER 92
#define CNFG_KEY_NUM_0 96
#define CNFG_KEY_NUM_1 97
#define CNFG_KEY_NUM_2 98
#define CNFG_KEY_NUM_3 99
#define CNFG_KEY_NUM_4 100
#define CNFG_KEY_NUM_5 101
#define CNFG_KEY_NUM_6 102
#define CNFG_KEY_NUM_7 103
#define CNFG_KEY_NUM_8 104
#define CNFG_KEY_NUM_9 105
#define CNFG_KEY_NUM_MULTIPLY 106
#define CNFG_KEY_NUM_ADD 107
#define CNFG_KEY_NUM_SEPARATOR 108
#define CNFG_KEY_NUM_SUBTRACT 109
#define CNFG_KEY_NUM_DECIMAL 110
#define CNFG_KEY_NUM_DIVIDE 111
#define CNFG_KEY_F1 112
#define CNFG_KEY_F2 113
#define CNFG_KEY_F3 114
#define CNFG_KEY_F4 115
#define CNFG_KEY_F5 116
#define CNFG_KEY_F6 117
#define CNFG_KEY_F7 118
#define CNFG_KEY_F8 119
#define CNFG_KEY_F9 120
#define CNFG_KEY_F10 121
#define CNFG_KEY_F11 122
#define CNFG_KEY_F12 123
#define CNFG_KEY_F13 124
#define CNFG_KEY_F14 125
#define CNFG_KEY_F15 126
#define CNFG_KEY_F16 127
#define CNFG_KEY_F17 128
#define CNFG_KEY_F18 129
#define CNFG_KEY_F19 130
#define CNFG_KEY_F20 131
#define CNFG_KEY_F21 132
#define CNFG_KEY_F22 133
#define CNFG_KEY_F23 134
#define CNFG_KEY_F24 135
#define CNFG_KEY_NUM_LOCK 144
#define CNFG_KEY_SCROLL_LOCK 145
#define CNFG_KEY_LEFT_SHIFT 160
#define CNFG_KEY_RIGHT_SHIFT 161
#define CNFG_KEY_LEFT_CONTROL 162
#define CNFG_KEY_RIGHT_CONTROL 163
#define CNFG_KEY_LEFT_ALT 164
#define CNFG_KEY_RIGHT_ALT 165
#else // most likely x11
#define CNFG_KEY_BACKSPACE 65288
#define CNFG_KEY_TAB 65289
#define CNFG_KEY_CLEAR 65291
#define CNFG_KEY_ENTER 65293
#define CNFG_KEY_SHIFT 65505
#define CNFG_KEY_CTRL 65507
#define CNFG_KEY_ALT 65513
#define CNFG_KEY_PAUSE 65299
#define CNFG_KEY_CAPS_LOCK 65509
#define CNFG_KEY_ESCAPE 65505
#define CNFG_KEY_SPACE 32
#define CNFG_KEY_PAGE_UP 65365
#define CNFG_KEY_PAGE_DOWN 65366
#define CNFG_KEY_END 65367
#define CNFG_KEY_HOME 65360
#define CNFG_KEY_LEFT_ARROW 65361
#define CNFG_KEY_TOP_ARROW 65362
#define CNFG_KEY_RIGHT_ARROW 65363
#define CNFG_KEY_BOTTOM_ARROW 65364
#define CNFG_KEY_SELECT 65376
#define CNFG_KEY_PRINT 65377
#define CNFG_KEY_EXECUTE 65378
#define CNFG_KEY_PRINT_SCREEN 64797
#define CNFG_KEY_INSERT 65379
#define CNFG_KEY_DELETE 65535
#define CNFG_KEY_HELP 65386
#define CNFG_KEY_LEFT_SUPER 65515
#define CNFG_KEY_RIGHT_SUPER 65516
#define CNFG_KEY_NUM_0 65456
#define CNFG_KEY_NUM_1 65457
#define CNFG_KEY_NUM_2 65458
#define CNFG_KEY_NUM_3 65459
#define CNFG_KEY_NUM_4 65460
#define CNFG_KEY_NUM_5 65461
#define CNFG_KEY_NUM_6 65462
#define CNFG_KEY_NUM_7 65463
#define CNFG_KEY_NUM_8 65464
#define CNFG_KEY_NUM_9 65465
#define CNFG_KEY_NUM_MULTIPLY 65450
#define CNFG_KEY_NUM_ADD 65451
#define CNFG_KEY_NUM_SEPARATOR 65452
#define CNFG_KEY_NUM_SUBTRACT 65453
#define CNFG_KEY_NUM_DECIMAL 65454
#define CNFG_KEY_NUM_DIVIDE 65455
#define CNFG_KEY_F1 65470
#define CNFG_KEY_F2 65471
#define CNFG_KEY_F3 65472
#define CNFG_KEY_F4 65473
#define CNFG_KEY_F5 65474
#define CNFG_KEY_F6 65475
#define CNFG_KEY_F7 65476
#define CNFG_KEY_F8 65477
#define CNFG_KEY_F9 65478
#define CNFG_KEY_F10 65479
#define CNFG_KEY_F11 65480
#define CNFG_KEY_F12 65481
#define CNFG_KEY_F13 65482
#define CNFG_KEY_F14 65483
#define CNFG_KEY_F15 65484
#define CNFG_KEY_F16 65485
#define CNFG_KEY_F17 65486
#define CNFG_KEY_F18 65487
#define CNFG_KEY_F19 65488
#define CNFG_KEY_F20 65489
#define CNFG_KEY_F21 65490
#define CNFG_KEY_F22 65491
#define CNFG_KEY_F23 65492
#define CNFG_KEY_F24 65493
#define CNFG_KEY_NUM_LOCK 65407
#define CNFG_KEY_SCROLL_LOCK 65300
#define CNFG_KEY_LEFT_SHIFT 65505
#define CNFG_KEY_RIGHT_SHIFT 65506
#define CNFG_KEY_LEFT_CONTROL 65507
#define CNFG_KEY_RIGHT_CONTROL 65508
#define CNFG_KEY_LEFT_ALT 65513
#define CNFG_KEY_RIGHT_ALT 65514
#define CNFG_X11_EXPOSE 0xff00 //65280
#endif
#ifdef CNFG3D
#ifndef __wasm__
#include <math.h>
#endif
#ifdef CNFG_USE_DOUBLE_FUNCTIONS
#define tdCOS cos
#define tdSIN sin
#define tdTAN tan
#define tdSQRT sqrt
#else
#define tdCOS cosf
#define tdSIN sinf
#define tdTAN tanf
#define tdSQRT sqrtf
#endif
#ifdef __wasm__
void tdMATCOPY( float * x, const float * y ); //Copy y into x
#else
#define tdMATCOPY(x,y) memcpy( x, y, 16*sizeof(float))
#endif
#define tdQ_PI 3.141592653589
#define tdDEGRAD (tdQ_PI/180.)
#define tdRADDEG (180./tdQ_PI)
//General Matrix Functions
void tdIdentity( float * f );
void tdZero( float * f );
void tdTranslate( float * f, float x, float y, float z ); //Operates ON f
void tdScale( float * f, float x, float y, float z ); //Operates ON f
void tdRotateAA( float * f, float angle, float x, float y, float z ); //Operates ON f
void tdRotateQuat( float * f, float qw, float qx, float qy, float qz ); //Operates ON f
void tdRotateEA( float * f, float x, float y, float z ); //Operates ON f
void tdMultiply( float * fin1, float * fin2, float * fout ); //Operates ON f
void tdPrint( const float * f );
void tdTransposeSelf( float * f );
//Specialty Matrix Functions
void tdPerspective( float fovy, float aspect, float zNear, float zFar, float * out ); //Sets, NOT OPERATES. (FOVX=degrees)
void tdLookAt( float * m, float * eye, float * at, float * up ); //Operates ON m
//General point functions
#define tdPSet( f, x, y, z ) { f[0] = x; f[1] = y; f[2] = z; }
void tdPTransform( const float * pin, float * f, float * pout );
void tdVTransform( const float * vin, float * f, float * vout );
void td4Transform( float * kin, float * f, float * kout );
void td4RTransform( float * kin, float * f, float * kout );
void tdNormalizeSelf( float * vin );
void tdCross( float * va, float * vb, float * vout );
float tdDistance( float * va, float * vb );
float tdDot( float * va, float * vb );
#define tdPSub( x, y, z ) { (z)[0] = (x)[0] - (y)[0]; (z)[1] = (x)[1] - (y)[1]; (z)[2] = (x)[2] - (y)[2]; }
#define tdPAdd( x, y, z ) { (z)[0] = (x)[0] + (y)[0]; (z)[1] = (x)[1] + (y)[1]; (z)[2] = (x)[2] + (y)[2]; }
//Stack Functionality
#define tdMATRIXMAXDEPTH 32
extern float * gSMatrix;
void tdPush();
void tdPop();
void tdMode( int mode );
#define tdMODELVIEW 0
#define tdPROJECTION 1
//Final stage tools
void tdSetViewport( float leftx, float topy, float rightx, float bottomy, float pixx, float pixy );
void tdFinalPoint( float * pin, float * pout );
float tdNoiseAt( int x, int y );
float tdFLerp( float a, float b, float t );
float tdPerlin2D( float x, float y );
#endif
extern const unsigned char RawdrawFontCharData[1405];
extern const unsigned short RawdrawFontCharMap[256];
#ifdef __cplusplus
};
#endif
#if defined( ANDROID ) || defined( __android__ )
#include "CNFGAndroid.h"
#endif
#ifdef CNFG_IMPLEMENTATION
#include "CNFG.c"
#endif
#endif

View file

@ -0,0 +1,542 @@
//Copyright 2012-2017 <>< Charles Lohr
//You may license this file under the MIT/x11, NewBSD, or any GPL license.
//This is a series of tools useful for software rendering.
//Use of this file with OpenGL is untested.
#ifdef CNFG3D
#include "CNFG.h"
#ifdef __wasm__
double sin( double v );
double cos( double v );
double tan( double v );
double sqrt( double v );
float sinf( float v );
float cosf( float v );
float tanf( float v );
float sqrtf( float v );
void tdMATCOPY( float * x, const float * y )
{
int i;
for( i = 0; i < 16; i++ ) x[i] = y[i];
}
#else
#include <string.h>
#include <stdio.h>
#endif
#ifdef CNFG3D_USE_OGL_MAJOR
#define m00 0
#define m10 1
#define m20 2
#define m30 3
#define m01 4
#define m11 5
#define m21 6
#define m31 7
#define m02 8
#define m12 9
#define m22 10
#define m32 11
#define m03 12
#define m13 13
#define m23 14
#define m33 15
#else
#define m00 0
#define m01 1
#define m02 2
#define m03 3
#define m10 4
#define m11 5
#define m12 6
#define m13 7
#define m20 8
#define m21 9
#define m22 10
#define m23 11
#define m30 12
#define m31 13
#define m32 14
#define m33 15
#endif
void tdIdentity( float * f )
{
f[m00] = 1; f[m01] = 0; f[m02] = 0; f[m03] = 0;
f[m10] = 0; f[m11] = 1; f[m12] = 0; f[m13] = 0;
f[m20] = 0; f[m21] = 0; f[m22] = 1; f[m23] = 0;
f[m30] = 0; f[m31] = 0; f[m32] = 0; f[m33] = 1;
}
void tdZero( float * f )
{
f[m00] = 0; f[m01] = 0; f[m02] = 0; f[m03] = 0;
f[m10] = 0; f[m11] = 0; f[m12] = 0; f[m13] = 0;
f[m20] = 0; f[m21] = 0; f[m22] = 0; f[m23] = 0;
f[m30] = 0; f[m31] = 0; f[m32] = 0; f[m33] = 0;
}
void tdTranslate( float * f, float x, float y, float z )
{
float ftmp[16];
tdIdentity(ftmp);
ftmp[m03] += x;
ftmp[m13] += y;
ftmp[m23] += z;
tdMultiply( f, ftmp, f );
}
void tdScale( float * f, float x, float y, float z )
{
#if 0
f[m00] *= x;
f[m01] *= x;
f[m02] *= x;
f[m03] *= x;
f[m10] *= y;
f[m11] *= y;
f[m12] *= y;
f[m13] *= y;
f[m20] *= z;
f[m21] *= z;
f[m22] *= z;
f[m23] *= z;
#endif
float ftmp[16];
tdIdentity(ftmp);
ftmp[m00] *= x;
ftmp[m11] *= y;
ftmp[m22] *= z;
tdMultiply( f, ftmp, f );
}
void tdRotateAA( float * f, float angle, float ix, float iy, float iz )
{
float ftmp[16];
float c = tdCOS( angle*tdDEGRAD );
float s = tdSIN( angle*tdDEGRAD );
float absin = tdSQRT( ix*ix + iy*iy + iz*iz );
float x = ix/absin;
float y = iy/absin;
float z = iz/absin;
ftmp[m00] = x*x*(1-c)+c;
ftmp[m01] = x*y*(1-c)-z*s;
ftmp[m02] = x*z*(1-c)+y*s;
ftmp[m03] = 0;
ftmp[m10] = y*x*(1-c)+z*s;
ftmp[m11] = y*y*(1-c)+c;
ftmp[m12] = y*z*(1-c)-x*s;
ftmp[m13] = 0;
ftmp[m20] = x*z*(1-c)-y*s;
ftmp[m21] = y*z*(1-c)+x*s;
ftmp[m22] = z*z*(1-c)+c;
ftmp[m23] = 0;
ftmp[m30] = 0;
ftmp[m31] = 0;
ftmp[m32] = 0;
ftmp[m33] = 1;
tdMultiply( f, ftmp, f );
}
void tdRotateQuat( float * f, float qw, float qx, float qy, float qz )
{
float ftmp[16];
//float qw2 = qw*qw;
float qx2 = qx*qx;
float qy2 = qy*qy;
float qz2 = qz*qz;
ftmp[m00] = 1 - 2*qy2 - 2*qz2;
ftmp[m01] = 2*qx*qy - 2*qz*qw;
ftmp[m02] = 2*qx*qz + 2*qy*qw;
ftmp[m03] = 0;
ftmp[m10] = 2*qx*qy + 2*qz*qw;
ftmp[m11] = 1 - 2*qx2 - 2*qz2;
ftmp[m12] = 2*qy*qz - 2*qx*qw;
ftmp[m13] = 0;
ftmp[m20] = 2*qx*qz - 2*qy*qw;
ftmp[m21] = 2*qy*qz + 2*qx*qw;
ftmp[m22] = 1 - 2*qx2 - 2*qy2;
ftmp[m23] = 0;
ftmp[m30] = 0;
ftmp[m31] = 0;
ftmp[m32] = 0;
ftmp[m33] = 1;
tdMultiply( f, ftmp, f );
}
void tdRotateEA( float * f, float x, float y, float z )
{
float ftmp[16];
//x,y,z must be negated for some reason
float X = -x*2*tdQ_PI/360; //Reduced calulation for speed
float Y = -y*2*tdQ_PI/360;
float Z = -z*2*tdQ_PI/360;
float cx = tdCOS(X);
float sx = tdSIN(X);
float cy = tdCOS(Y);
float sy = tdSIN(Y);
float cz = tdCOS(Z);
float sz = tdSIN(Z);
//Row major (unless CNFG3D_USE_OGL_MAJOR is selected)
//manually transposed
ftmp[m00] = cy*cz;
ftmp[m10] = (sx*sy*cz)-(cx*sz);
ftmp[m20] = (cx*sy*cz)+(sx*sz);
ftmp[m30] = 0;
ftmp[m01] = cy*sz;
ftmp[m11] = (sx*sy*sz)+(cx*cz);
ftmp[m21] = (cx*sy*sz)-(sx*cz);
ftmp[m31] = 0;
ftmp[m02] = -sy;
ftmp[m12] = sx*cy;
ftmp[m22] = cx*cy;
ftmp[m32] = 0;
ftmp[m03] = 0;
ftmp[m13] = 0;
ftmp[m23] = 0;
ftmp[m33] = 1;
tdMultiply( f, ftmp, f );
}
void tdMultiply( float * fin1, float * fin2, float * fout )
{
float fotmp[16];
int i, k;
#ifdef CNFG3D_USE_OGL_MAJOR
fotmp[m00] = fin1[m00] * fin2[m00] + fin1[m01] * fin2[m10] + fin1[m02] * fin2[m20] + fin1[m03] * fin2[m30];
fotmp[m01] = fin1[m00] * fin2[m01] + fin1[m01] * fin2[m11] + fin1[m02] * fin2[m21] + fin1[m03] * fin2[m31];
fotmp[m02] = fin1[m00] * fin2[m02] + fin1[m01] * fin2[m12] + fin1[m02] * fin2[m22] + fin1[m03] * fin2[m32];
fotmp[m03] = fin1[m00] * fin2[m03] + fin1[m01] * fin2[m13] + fin1[m02] * fin2[m23] + fin1[m03] * fin2[m33];
fotmp[m10] = fin1[m10] * fin2[m00] + fin1[m11] * fin2[m10] + fin1[m12] * fin2[m20] + fin1[m13] * fin2[m30];
fotmp[m11] = fin1[m10] * fin2[m01] + fin1[m11] * fin2[m11] + fin1[m12] * fin2[m21] + fin1[m13] * fin2[m31];
fotmp[m12] = fin1[m10] * fin2[m02] + fin1[m11] * fin2[m12] + fin1[m12] * fin2[m22] + fin1[m13] * fin2[m32];
fotmp[m13] = fin1[m10] * fin2[m03] + fin1[m11] * fin2[m13] + fin1[m12] * fin2[m23] + fin1[m13] * fin2[m33];
fotmp[m20] = fin1[m20] * fin2[m00] + fin1[m21] * fin2[m10] + fin1[m22] * fin2[m20] + fin1[m23] * fin2[m30];
fotmp[m21] = fin1[m20] * fin2[m01] + fin1[m21] * fin2[m11] + fin1[m22] * fin2[m21] + fin1[m23] * fin2[m31];
fotmp[m22] = fin1[m20] * fin2[m02] + fin1[m21] * fin2[m12] + fin1[m22] * fin2[m22] + fin1[m23] * fin2[m32];
fotmp[m23] = fin1[m20] * fin2[m03] + fin1[m21] * fin2[m13] + fin1[m22] * fin2[m23] + fin1[m23] * fin2[m33];
fotmp[m30] = fin1[m30] * fin2[m00] + fin1[m31] * fin2[m10] + fin1[m32] * fin2[m20] + fin1[m33] * fin2[m30];
fotmp[m31] = fin1[m30] * fin2[m01] + fin1[m31] * fin2[m11] + fin1[m32] * fin2[m21] + fin1[m33] * fin2[m31];
fotmp[m32] = fin1[m30] * fin2[m02] + fin1[m31] * fin2[m12] + fin1[m32] * fin2[m22] + fin1[m33] * fin2[m32];
fotmp[m33] = fin1[m30] * fin2[m03] + fin1[m31] * fin2[m13] + fin1[m32] * fin2[m23] + fin1[m33] * fin2[m33];
#else
for( i = 0; i < 16; i++ )
{
int xp = i & 0x03;
int yp = i & 0x0c;
fotmp[i] = 0;
for( k = 0; k < 4; k++ )
{
fotmp[i] += fin1[yp+k] * fin2[(k<<2)|xp];
}
}
#endif
tdMATCOPY( fout, fotmp );
}
#ifndef __wasm__
void tdPrint( const float * f )
{
int i;
printf( "{\n" );
#ifdef CNFG3D_USE_OGL_MAJOR
for( i = 0; i < 4; i++ )
{
printf( " %f, %f, %f, %f\n", f[0+i], f[4+i], f[8+i], f[12+i] );
}
#else
for( i = 0; i < 16; i+=4 )
{
printf( " %f, %f, %f, %f\n", f[0+i], f[1+i], f[2+i], f[3+i] );
}
#endif
printf( "}\n" );
}
#endif
void tdTransposeSelf( float * f )
{
float fout[16];
fout[m00] = f[m00]; fout[m01] = f[m10]; fout[m02] = f[m20]; fout[m03] = f[m30];
fout[m10] = f[m01]; fout[m11] = f[m11]; fout[m12] = f[m21]; fout[m13] = f[m31];
fout[m20] = f[m02]; fout[m21] = f[m12]; fout[m22] = f[m22]; fout[m23] = f[m32];
fout[m30] = f[m03]; fout[m31] = f[m13]; fout[m32] = f[m23]; fout[m33] = f[m33];
tdMATCOPY( f, fout );
}
void tdPerspective( float fovy, float aspect, float zNear, float zFar, float * out )
{
float f = 1./tdTAN(fovy * tdQ_PI / 360.0);
out[m00] = f/aspect; out[m01] = 0; out[m02] = 0; out[m03] = 0;
out[m10] = 0; out[m11] = f; out[m12] = 0; out[m13] = 0;
out[m20] = 0; out[m21] = 0;
out[m22] = (zFar + zNear)/(zNear - zFar);
out[m23] = 2*zFar*zNear /(zNear - zFar);
out[m30] = 0; out[m31] = 0; out[m32] = -1; out[m33] = 0;
}
void tdLookAt( float * m, float * eye, float * at, float * up )
{
float out[16];
float F[3] = { at[0] - eye[0], at[1] - eye[1], at[2] - eye[2] };
float fdiv = 1./tdSQRT( F[0]*F[0] + F[1]*F[1] + F[2]*F[2] );
float f[3] = { F[0]*fdiv, F[1]*fdiv, F[2]*fdiv };
float udiv = 1./tdSQRT( up[0]*up[0] + up[1]*up[1] + up[2]*up[2] );
float UP[3] = { up[0]*udiv, up[1]*udiv, up[2]*udiv };
float s[3];
float u[3];
tdCross( f, UP, s );
tdCross( s, f, u );
out[m00] = s[0]; out[m01] = s[1]; out[m02] = s[2]; out[m03] = 0;
out[m10] = u[0]; out[m11] = u[1]; out[m12] = u[2]; out[m13] = 0;
out[m20] = -f[0];out[m21] =-f[1]; out[m22] =-f[2]; out[m23] = 0;
out[m30] = 0; out[m31] = 0; out[m32] = 0; out[m33] = 1;
tdMultiply( m, out, m );
tdTranslate( m, -eye[0], -eye[1], -eye[2] );
}
void tdPTransform( const float * pin, float * f, float * pout )
{
float ptmp[2];
ptmp[0] = pin[0] * f[m00] + pin[1] * f[m01] + pin[2] * f[m02] + f[m03];
ptmp[1] = pin[0] * f[m10] + pin[1] * f[m11] + pin[2] * f[m12] + f[m13];
pout[2] = pin[0] * f[m20] + pin[1] * f[m21] + pin[2] * f[m22] + f[m23];
pout[0] = ptmp[0];
pout[1] = ptmp[1];
}
void tdVTransform( const float * pin, float * f, float * pout )
{
float ptmp[2];
ptmp[0] = pin[0] * f[m00] + pin[1] * f[m01] + pin[2] * f[m02];
ptmp[1] = pin[0] * f[m10] + pin[1] * f[m11] + pin[2] * f[m12];
pout[2] = pin[0] * f[m20] + pin[1] * f[m21] + pin[2] * f[m22];
pout[0] = ptmp[0];
pout[1] = ptmp[1];
}
void td4Transform( float * pin, float * f, float * pout )
{
float ptmp[3];
ptmp[0] = pin[0] * f[m00] + pin[1] * f[m01] + pin[2] * f[m02] + pin[3] * f[m03];
ptmp[1] = pin[0] * f[m10] + pin[1] * f[m11] + pin[2] * f[m12] + pin[3] * f[m13];
ptmp[2] = pin[0] * f[m20] + pin[1] * f[m21] + pin[2] * f[m22] + pin[3] * f[m23];
pout[3] = pin[0] * f[m30] + pin[1] * f[m31] + pin[2] * f[m32] + pin[3] * f[m33];
pout[0] = ptmp[0];
pout[1] = ptmp[1];
pout[2] = ptmp[2];
}
void td4RTransform( float * pin, float * f, float * pout )
{
float ptmp[3];
ptmp[0] = pin[0] * f[m00] + pin[1] * f[m10] + pin[2] * f[m20] + pin[3] * f[m30];
ptmp[1] = pin[0] * f[m01] + pin[1] * f[m11] + pin[2] * f[m21] + pin[3] * f[m31];
ptmp[2] = pin[0] * f[m02] + pin[1] * f[m12] + pin[2] * f[m22] + pin[3] * f[m32];
pout[3] = pin[0] * f[m03] + pin[1] * f[m13] + pin[2] * f[m23] + pin[3] * f[m33];
pout[0] = ptmp[0];
pout[1] = ptmp[1];
pout[2] = ptmp[2];
}
void tdNormalizeSelf( float * vin )
{
float vsq = 1./tdSQRT(vin[0]*vin[0] + vin[1]*vin[1] + vin[2]*vin[2]);
vin[0] *= vsq;
vin[1] *= vsq;
vin[2] *= vsq;
}
void tdCross( float * va, float * vb, float * vout )
{
float vtmp[2];
vtmp[0] = va[1] * vb[2] - va[2] * vb[1];
vtmp[1] = va[2] * vb[0] - va[0] * vb[2];
vout[2] = va[0] * vb[1] - va[1] * vb[0];
vout[0] = vtmp[0];
vout[1] = vtmp[1];
}
float tdDistance( float * va, float * vb )
{
float dx = va[0]-vb[0];
float dy = va[1]-vb[1];
float dz = va[2]-vb[2];
return tdSQRT(dx*dx + dy*dy + dz*dz);
}
float tdDot( float * va, float * vb )
{
return va[0]*vb[0] + va[1]*vb[1] + va[2]*vb[2];
}
//Stack functionality.
static float gsMatricies[2][tdMATRIXMAXDEPTH][16];
float * gSMatrix = gsMatricies[0][0];
static int gsMMode;
static int gsMPlace[2];
void tdPush()
{
if( gsMPlace[gsMMode] > tdMATRIXMAXDEPTH - 2 )
return;
tdMATCOPY( gsMatricies[gsMMode][gsMPlace[gsMMode] + 1], gsMatricies[gsMMode][gsMPlace[gsMMode]] );
gsMPlace[gsMMode]++;
gSMatrix = gsMatricies[gsMMode][gsMPlace[gsMMode]];
}
void tdPop()
{
if( gsMPlace[gsMMode] < 1 )
return;
gsMPlace[gsMMode]--;
gSMatrix = gsMatricies[gsMMode][gsMPlace[gsMMode]];
}
void tdMode( int mode )
{
if( mode < 0 || mode > 1 )
return;
gsMMode = mode;
gSMatrix = gsMatricies[gsMMode][gsMPlace[gsMMode]];
}
static float translateX;
static float translateY;
static float scaleX;
static float scaleY;
void tdSetViewport( float leftx, float topy, float rightx, float bottomy, float pixx, float pixy )
{
translateX = leftx;
translateY = bottomy;
scaleX = pixx/(rightx-leftx);
scaleY = pixy/(topy-bottomy);
}
void tdFinalPoint( float * pin, float * pout )
{
float tdin[4] = { pin[0], pin[1], pin[2], 1. };
float tmp[4];
td4Transform( tdin, gsMatricies[0][gsMPlace[0]], tmp );
// printf( "XFORM1Out: %f %f %f %f\n", tmp[0], tmp[1], tmp[2], tmp[3] );
td4Transform( tmp, gsMatricies[1][gsMPlace[1]], tmp );
// printf( "XFORM2Out: %f %f %f %f\n", tmp[0], tmp[1], tmp[2], tmp[3] );
pout[0] = (tmp[0]/tmp[3] - translateX) * scaleX;
pout[1] = (tmp[1]/tmp[3] - translateY) * scaleY;
pout[2] = tmp[2]/tmp[3];
// printf( "XFORMFOut: %f %f %f\n", pout[0], pout[1], pout[2] );
}
float tdNoiseAt( int x, int y )
{
return ((x*13241*y + y * 33455927)%9293) / 4646. - 1.0;
}
static inline float tdFade( float f )
{
float ft3 = f*f*f;
return ft3 * 10 - ft3 * f * 15 + 6 * ft3 * f * f;
}
float tdFLerp( float a, float b, float t )
{
float fr = tdFade( t );
return a * (1.-fr) + b * fr;
}
static inline float tdFNoiseAt( float x, float y )
{
int ix = x;
int iy = y;
float fx = x - ix;
float fy = y - iy;
float a = tdNoiseAt( ix, iy );
float b = tdNoiseAt( ix+1, iy );
float c = tdNoiseAt( ix, iy+1 );
float d = tdNoiseAt( ix+1, iy+1 );
float top = tdFLerp( a, b, fx );
float bottom = tdFLerp( c, d, fx );
return tdFLerp( top, bottom, fy );
}
float tdPerlin2D( float x, float y )
{
int ndepth = 5;
int depth;
float ret = 0;
for( depth = 0; depth < ndepth; depth++ )
{
float nx = x / (1<<(ndepth-depth-1));
float ny = y / (1<<(ndepth-depth-1));
ret += tdFNoiseAt( nx, ny ) / (1<<(depth+1));
}
return ret;
}
#endif

View file

@ -0,0 +1,217 @@
#ifndef _CNFG_ANDROID_H
#define _CNFG_ANDROID_H
//This file contains the additional functions that are available on the Android platform.
//In order to build rawdraw for Android, please compile CNFGEGLDriver.c with -DANDROID
// Tricky: Android headers are confused by c++ if linking statically.
#ifdef __cplusplus
extern "C" {
int __system_property_get(const char* __name, char* __value);
};
#endif
extern struct android_app * gapp;
void AndroidMakeFullscreen();
const char* AndroidGetExternalFilesDir();
int AndroidHasPermissions(const char* perm_name);
void AndroidRequestAppPermissions(const char * perm);
void AndroidDisplayKeyboard(int pShow);
int AndroidGetUnicodeChar( int keyCode, int metaState );
void AndroidSendToBack( int param );
extern int android_sdk_version; //Derived at start from property ro.build.version.sdk
extern int android_width, android_height;
extern int UpdateScreenWithBitmapOffsetX;
extern int UpdateScreenWithBitmapOffsetY;
extern void (*HandleCustomEventCallback)();
extern void (*HandleWindowTermination)();
// If you need them, these are the names of raw EGL symbols.
//extern EGLDisplay egl_display;
//extern EGLSurface egl_surface;
//extern EGLContext egl_context;
//extern EGLConfig egl_config;
//You must implement these.
void HandleResume();
void HandleSuspend();
//Departures:
// HandleMotion's "mask" parameter is actually just an index, not a mask
// CNFGSetup / CNFGSetupFullScreen only controls whether or not the navigation
// decoration is removed. Fullscreen means *full screen* To choose fullscreen
// or not fullscrene, modify, in your AndroidManifest.xml file, the application
// section to either contain or not contain:
// android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
// For debugging:
#if defined( ANDROID ) && !defined( __cplusplus )
#include <jni.h>
static inline void PrintClassOfObject( jobject bundle )
{
const struct JNINativeInterface * env = 0;
const struct JNINativeInterface ** envptr = &env;
const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm;
const struct JNIInvokeInterface * jnii = *jniiptr;
jnii->AttachCurrentThread( jniiptr, &envptr, NULL);
env = (*envptr);
jclass myclass = env->GetObjectClass( envptr, bundle );
jmethodID mid = env->GetMethodID( envptr, myclass, "getClass", "()Ljava/lang/Class;");
jobject clsObj = env->CallObjectMethod( envptr, bundle, mid );
jclass clazzz = env->GetObjectClass( envptr, clsObj );
mid = env->GetMethodID(envptr, clazzz, "getName", "()Ljava/lang/String;");
jstring strObj = (jstring)env->CallObjectMethod( envptr, clsObj, mid);
const char * name = env->GetStringUTFChars( envptr, strObj, 0);
printf( "Class type: %s\n", name );
env->DeleteLocalRef( envptr, myclass );
env->DeleteLocalRef( envptr, clsObj );
env->DeleteLocalRef( envptr, clazzz );
env->ReleaseStringUTFChars(envptr, strObj, name);
env->DeleteLocalRef( envptr, strObj );
}
static inline void PrintObjectString( jobject bundle )
{
const struct JNINativeInterface * env = 0;
const struct JNINativeInterface ** envptr = &env;
const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm;
const struct JNIInvokeInterface * jnii = *jniiptr;
jnii->AttachCurrentThread( jniiptr, &envptr, NULL);
env = (*envptr);
jclass myclass = env->GetObjectClass( envptr, bundle );
jmethodID toStringMethod = env->GetMethodID( envptr, myclass, "toString", "()Ljava/lang/String;");
jstring strObjDescr = (jstring)env->CallObjectMethod( envptr, bundle, toStringMethod);
const char *descr = env->GetStringUTFChars( envptr, strObjDescr, 0);
printf( "String: %s\n", descr );
env->DeleteLocalRef( envptr, myclass );
env->ReleaseStringUTFChars( envptr, strObjDescr, descr );
env->DeleteLocalRef( envptr, strObjDescr );
}
static inline void DumpObjectClassProperties( jobject objToDump )
{
const struct JNINativeInterface * env = 0;
const struct JNINativeInterface ** envptr = &env;
const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm;
const struct JNIInvokeInterface * jnii = *jniiptr;
jnii->AttachCurrentThread( jniiptr, &envptr, NULL);
env = (*envptr);
jclass mpclass = env->GetObjectClass( envptr, objToDump );
jmethodID midGetClass = env->GetMethodID( envptr, mpclass, "getClass", "()Ljava/lang/Class;");
jclass ClassClass = env->FindClass(envptr, "java/lang/Class");
jobject clsObj = env->CallObjectMethod( envptr, objToDump, midGetClass);
jmethodID gnid = env->GetMethodID(envptr, ClassClass, "getName", "()Ljava/lang/String;");
jstring nameObj = (jstring)env->CallObjectMethod( envptr, clsObj, gnid);
const char *name = env->GetStringUTFChars( envptr, nameObj, 0);
printf( "Class Name: %s\n", name );
env->ReleaseStringUTFChars(envptr, nameObj, name);
env->DeleteLocalRef( envptr, nameObj );
jmethodID getMethodsMethod = env->GetMethodID( envptr, ClassClass, "getMethods","()[Ljava/lang/reflect/Method;");
jobject jobjArray = env->CallObjectMethod( envptr, clsObj, getMethodsMethod );
jclass MethodType = env->FindClass(envptr, "java/lang/reflect/Method");
jclass TypeType = env->FindClass(envptr, "java/lang/reflect/Type");
jmethodID tnamemid = env->GetMethodID( envptr, TypeType, "getTypeName", "()Ljava/lang/String;" );
jmethodID getFieldsMethod = env->GetMethodID( envptr, ClassClass, "getFields","()[Ljava/lang/reflect/Field;");
jobject jobjArrayFields = env->CallObjectMethod( envptr, clsObj, getFieldsMethod );
jsize len = env->GetArrayLength(envptr, jobjArray);
jsize i;
printf( "Methods:\n" );
for (i = 0 ; i < len ; i++) {
jobject _strMethod = env->GetObjectArrayElement( envptr, jobjArray, i ) ;
jclass _methodClazz = env->GetObjectClass(envptr, _strMethod) ;
jmethodID mid = env->GetMethodID(envptr, _methodClazz , "getName", "()Ljava/lang/String;" );
jmethodID getGenericParameterTypes = env->GetMethodID( envptr, _methodClazz, "getGenericParameterTypes","()[Ljava/lang/reflect/Type;");
jmethodID getReturnTypeMethod = env->GetMethodID( envptr, _methodClazz, "getGenericReturnType","()Ljava/lang/reflect/Type;");
jstring _name = (jstring)env->CallObjectMethod( envptr, _strMethod , mid ) ;
const char *str = env->GetStringUTFChars(envptr, _name, 0);
printf(" %s( ", str);
jobject types = env->CallObjectMethod( envptr, _strMethod, getGenericParameterTypes );
jsize mlen = env->GetArrayLength(envptr, types);
jsize mi;
for( mi = 0; mi < mlen; mi++ )
{
jobject typeo = env->GetObjectArrayElement( envptr, types, mi );
jstring _tn = (jstring)env->CallObjectMethod( envptr, typeo, tnamemid );
const char * str = env->GetStringUTFChars(envptr, _tn, 0);
printf("%s%s ", str, (mi == mlen-1)?"":"," );
env->ReleaseStringUTFChars(envptr, _tn, str);
env->DeleteLocalRef( envptr, _tn );
env->DeleteLocalRef( envptr, typeo );
}
jobject rtype = env->CallObjectMethod( envptr, _strMethod, getReturnTypeMethod );
jstring _tn = (jstring)env->CallObjectMethod( envptr, rtype, tnamemid );
const char * strret = env->GetStringUTFChars(envptr, _tn, 0);
printf(") -> %s\n", strret);
env->ReleaseStringUTFChars(envptr, _tn, strret);
env->DeleteLocalRef( envptr, _tn );
env->DeleteLocalRef( envptr, rtype );
env->DeleteLocalRef( envptr, types );
env->ReleaseStringUTFChars(envptr, _name, str);
env->DeleteLocalRef( envptr, _methodClazz );
env->DeleteLocalRef( envptr, _strMethod );
env->DeleteLocalRef( envptr, _name );
}
len = env->GetArrayLength(envptr, jobjArrayFields);
printf( "Fields:\n" );
for ( i = 0; i < len; i++) {
jobject _strField = env->GetObjectArrayElement( envptr, jobjArrayFields, i) ;
jclass _methodClazz = env->GetObjectClass(envptr, _strField );
jmethodID mid = env->GetMethodID(envptr, _methodClazz , "getName", "()Ljava/lang/String;") ;
jstring _name = (jstring)env->CallObjectMethod( envptr, _strField , mid ) ;
const char *str = env->GetStringUTFChars(envptr, _name, 0);
jmethodID getTypeMethod = env->GetMethodID( envptr, _methodClazz, "getGenericType","()Ljava/lang/reflect/Type;");
jobject rtype = env->CallObjectMethod( envptr, _strField, getTypeMethod );
jstring _tn = (jstring)env->CallObjectMethod( envptr, rtype, tnamemid );
const char * strret = env->GetStringUTFChars(envptr, _tn, 0);
printf(" %s -> %s\n", str, strret );
env->ReleaseStringUTFChars(envptr, _name, str);
env->ReleaseStringUTFChars(envptr, _tn, strret);
env->DeleteLocalRef( envptr, _methodClazz );
env->DeleteLocalRef( envptr, _strField );
env->DeleteLocalRef( envptr, _name );
env->DeleteLocalRef( envptr, _tn );
}
env->DeleteLocalRef( envptr, TypeType );
env->DeleteLocalRef( envptr, MethodType );
env->DeleteLocalRef( envptr, clsObj );
env->DeleteLocalRef( envptr, ClassClass );
env->DeleteLocalRef( envptr, jobjArrayFields );
env->DeleteLocalRef( envptr, jobjArray );
env->DeleteLocalRef( envptr, mpclass );
}
#endif
#endif

View file

@ -0,0 +1,866 @@
/*
* Copyright (c) 2011-2013 Luc Verhaegen <libv@skynet.be>
* Copyright (c) 2018-2020 <>< Charles Lohr
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sub license,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial portions
* of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#if defined( __android__ ) && !defined( ANDROID )
#define ANDROID
#endif
//Note: This interface provides the following two things privately.
//you may "extern" them in your code.
#ifdef ANDROID
#include "CNFGAndroid.h"
extern struct android_app * gapp;
static int OGLESStarted;
void (*HandleCustomEventCallback)();
int android_width, android_height;
int override_android_screen_dimensons = 0;
int android_sdk_version;
#include <android_native_app_glue.h>
#include <jni.h>
#include <android/native_activity.h>
#define ERRLOG(...) printf( __VA_ARGS__ );
#else
#define ERRLOG(...) fprintf( stderr, __VA_ARGS__ );
#endif
#include "CNFG.h"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <EGL/egl.h>
#ifdef ANDROID
#include <GLES3/gl3.h>
#else
#include <GLES2/gl2.h>
#endif
#define EGL_ZBITS 16
#define EGL_IMMEDIATE_SIZE 2048
#ifdef USE_EGL_X
#error This feature has never been completed or tested.
Display *XDisplay;
Window XWindow;
#else
typedef enum
{
FBDEV_PIXMAP_DEFAULT = 0,
FBDEV_PIXMAP_SUPPORTS_UMP = (1<<0),
FBDEV_PIXMAP_ALPHA_FORMAT_PRE = (1<<1),
FBDEV_PIXMAP_COLORSPACE_sRGB = (1<<2),
FBDEV_PIXMAP_EGL_MEMORY = (1<<3) /* EGL allocates/frees this memory */
} fbdev_pixmap_flags;
typedef struct fbdev_window
{
unsigned short width;
unsigned short height;
} fbdev_window;
typedef struct fbdev_pixmap
{
unsigned int height;
unsigned int width;
unsigned int bytes_per_pixel;
unsigned char buffer_size;
unsigned char red_size;
unsigned char green_size;
unsigned char blue_size;
unsigned char alpha_size;
unsigned char luminance_size;
fbdev_pixmap_flags flags;
unsigned short *data;
unsigned int format; /* extra format information in case rgbal is not enough, especially for YUV formats */
} fbdev_pixmap;
#if defined( ANDROID )
EGLNativeWindowType native_window;
#else
struct fbdev_window native_window;
#endif
#endif
static EGLint const config_attribute_list[] = {
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_BUFFER_SIZE, 32,
EGL_STENCIL_SIZE, 0,
EGL_DEPTH_SIZE, EGL_ZBITS,
//EGL_SAMPLES, 1,
#ifdef ANDROID
#if ANDROIDVERSION >= 28
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
#else
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
#endif
#else
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PIXMAP_BIT,
#endif
EGL_NONE
};
static EGLint window_attribute_list[] = {
EGL_NONE
};
static const EGLint context_attribute_list[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
EGLDisplay egl_display;
EGLSurface egl_surface;
EGLContext egl_context;
EGLConfig egl_config;
void CNFGSetVSync( int vson )
{
eglSwapInterval(egl_display, vson);
}
static short iLastInternalW, iLastInternalH;
void CNFGSwapBuffers()
{
if ( egl_display == EGL_NO_DISPLAY ) return;
CNFGFlushRender();
eglSwapBuffers(egl_display, egl_surface);
#ifdef ANDROID
if( !override_android_screen_dimensons )
{
android_width = ANativeWindow_getWidth( native_window );
android_height = ANativeWindow_getHeight( native_window );
}
glViewport( 0, 0, android_width, android_height );
if( iLastInternalW != android_width || iLastInternalH != android_height )
CNFGInternalResize( iLastInternalW=android_width, iLastInternalH=android_height );
#endif
}
void CNFGGetDimensions( short * x, short * y )
{
#ifdef ANDROID
*x = android_width;
*y = android_height;
#else
*x = native_window.width;
*y = native_window.height;
#endif
if( *x != iLastInternalW || *y != iLastInternalH )
CNFGInternalResize( iLastInternalW=*x, iLastInternalH=*y );
}
int CNFGSetup( const char * WindowName, int w, int h )
{
EGLint egl_major, egl_minor;
EGLint num_config;
//This MUST be called before doing any initialization.
int events;
while( !OGLESStarted )
{
struct android_poll_source* source;
if (ALooper_pollOnce( 0, 0, &events, (void**)&source) >= 0)
{
if (source != NULL) source->process(gapp, source);
}
}
#ifdef USE_EGL_X
XDisplay = XOpenDisplay(NULL);
if (!XDisplay) {
ERRLOG( "Error: failed to open X display.\n");
return -1;
}
Window XRoot = DefaultRootWindow(XDisplay);
XSetWindowAttributes XWinAttr;
XWinAttr.event_mask = ExposureMask | PointerMotionMask;
XWindow = XCreateWindow(XDisplay, XRoot, 0, 0, WIDTH, HEIGHT, 0,
CopyFromParent, InputOutput,
CopyFromParent, CWEventMask, &XWinAttr);
Atom XWMDeleteMessage =
XInternAtom(XDisplay, "WM_DELETE_WINDOW", False);
XMapWindow(XDisplay, XWindow);
XStoreName(XDisplay, XWindow, "Mali libs test");
XSetWMProtocols(XDisplay, XWindow, &XWMDeleteMessage, 1);
egl_display = eglGetDisplay((EGLNativeDisplayType) XDisplay);
#else
#ifndef ANDROID
if( w >= 1 && h >= 1 )
{
native_window.width = w;
native_window.height =h;
}
#endif
egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
#endif
if (egl_display == EGL_NO_DISPLAY) {
ERRLOG( "Error: No display found!\n");
return -1;
}
if (!eglInitialize(egl_display, &egl_major, &egl_minor)) {
ERRLOG( "Error: eglInitialise failed!\n");
return -1;
}
printf("EGL Version: \"%s\"\n",
eglQueryString(egl_display, EGL_VERSION));
printf("EGL Vendor: \"%s\"\n",
eglQueryString(egl_display, EGL_VENDOR));
printf("EGL Extensions: \"%s\"\n",
eglQueryString(egl_display, EGL_EXTENSIONS));
eglChooseConfig(egl_display, config_attribute_list, &egl_config, 1,
&num_config);
printf( "Config: %d\n", num_config );
printf( "Creating Context\n" );
egl_context = eglCreateContext(egl_display, egl_config, EGL_NO_CONTEXT,
// NULL );
context_attribute_list);
if (egl_context == EGL_NO_CONTEXT) {
ERRLOG( "Error: eglCreateContext failed: 0x%08X\n",
eglGetError());
return -1;
}
printf( "Context Created %p\n", egl_context );
#ifdef USE_EGL_X
egl_surface = eglCreateWindowSurface(egl_display, egl_config, XWindow,
window_attribute_list);
#else
if( native_window && !gapp->window )
{
printf( "WARNING: App restarted without a window. Cannot progress.\n" );
exit( 0 );
}
printf( "Getting Surface %p\n", native_window = gapp->window );
if( !native_window )
{
printf( "FAULT: Cannot get window\n" );
return -5;
}
if( w <= 0 || h <= 0 )
{
android_width = ANativeWindow_getWidth( native_window );
android_height = ANativeWindow_getHeight( native_window );
}
else
{
override_android_screen_dimensons = 1;
android_width = w;
android_height = h;
}
printf( "Width/Height: %dx%d\n", android_width, android_height );
egl_surface = eglCreateWindowSurface(egl_display, egl_config,
#ifdef ANDROID
gapp->window,
#else
(EGLNativeWindowType)&native_window,
#endif
window_attribute_list);
#endif
printf( "Got Surface: %p\n", egl_surface );
if (egl_surface == EGL_NO_SURFACE) {
ERRLOG( "Error: eglCreateWindowSurface failed: "
"0x%08X\n", eglGetError());
return -1;
}
#ifndef ANDROID
int width, height;
if (!eglQuerySurface(egl_display, egl_surface, EGL_WIDTH, &width) ||
!eglQuerySurface(egl_display, egl_surface, EGL_HEIGHT, &height)) {
ERRLOG( "Error: eglQuerySurface failed: 0x%08X\n",
eglGetError());
return -1;
}
printf("Surface size: %dx%d\n", width, height);
native_window.width = width;
native_window.height = height;
#endif
if (!eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context)) {
ERRLOG( "Error: eglMakeCurrent() failed: 0x%08X\n",
eglGetError());
return -1;
}
printf("GL Vendor: \"%s\"\n", glGetString(GL_VENDOR));
printf("GL Renderer: \"%s\"\n", glGetString(GL_RENDERER));
printf("GL Version: \"%s\"\n", glGetString(GL_VERSION));
printf("GL Extensions: \"%s\"\n", glGetString(GL_EXTENSIONS));
CNFGSetupBatchInternal();
{
short dummyx, dummyy;
CNFGGetDimensions( &dummyx, &dummyy );
}
return 0;
}
void CNFGSetupFullscreen( const char * WindowName, int screen_number )
{
//Removes decoration, must be called before setup.
AndroidMakeFullscreen();
CNFGSetup( WindowName, -1, -1 );
}
int debuga, debugb, debugc;
int32_t handle_input(struct android_app* app, AInputEvent* event)
{
#ifdef ANDROID
//Potentially do other things here.
if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION)
{
int action = AMotionEvent_getAction( event );
int whichsource = action >> 8;
action &= AMOTION_EVENT_ACTION_MASK;
size_t pointerCount = AMotionEvent_getPointerCount(event);
for (size_t i = 0; i < pointerCount; ++i)
{
int x, y, index;
x = AMotionEvent_getX(event, i);
y = AMotionEvent_getY(event, i);
index = AMotionEvent_getPointerId( event, i );
if( action == AMOTION_EVENT_ACTION_POINTER_DOWN || action == AMOTION_EVENT_ACTION_DOWN )
{
int id = index;
if( action == AMOTION_EVENT_ACTION_POINTER_DOWN && id != whichsource ) continue;
HandleButton( x, y, id, 1 );
ANativeActivity_showSoftInput( gapp->activity, ANATIVEACTIVITY_SHOW_SOFT_INPUT_FORCED );
}
else if( action == AMOTION_EVENT_ACTION_POINTER_UP || action == AMOTION_EVENT_ACTION_UP || action == AMOTION_EVENT_ACTION_CANCEL )
{
int id = index;
if( action == AMOTION_EVENT_ACTION_POINTER_UP && id != whichsource ) continue;
HandleButton( x, y, id, 0 );
}
else if( action == AMOTION_EVENT_ACTION_MOVE )
{
HandleMotion( x, y, index );
}
}
return 1;
}
else if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_KEY)
{
int code = AKeyEvent_getKeyCode(event);
#ifdef ANDROID_USE_SCANCODES
HandleKey( code, AKeyEvent_getAction(event) );
#else
int unicode = AndroidGetUnicodeChar( code, AMotionEvent_getMetaState( event ) );
if( unicode )
HandleKey( unicode, AKeyEvent_getAction(event) );
else
{
HandleKey( code, !AKeyEvent_getAction(event) );
return (code == 4)?1:0; //don't override functionality.
}
#endif
return 1;
}
#endif
return 0;
}
int CNFGHandleInput()
{
#ifdef ANDROID
int events;
struct android_poll_source* source;
while( ALooper_pollOnce( 0, 0, &events, (void**)&source) >= 0 )
{
if (source != NULL)
{
source->process(gapp, source);
}
}
#endif
#ifdef USE_EGL_X
while (1) {
XEvent event;
XNextEvent(XDisplay, &event);
if ((event.type == MotionNotify) ||
(event.type == Expose))
Redraw(width, height);
else if (event.type == ClientMessage) {
if (event.xclient.data.l[0] == XWMDeleteMessage)
break;
}
}
XSetWMProtocols(XDisplay, XWindow, &XWMDeleteMessage, 0);
#endif
return 1;
}
#ifdef ANDROID
void (*HandleWindowTermination)();
void handle_cmd(struct android_app* app, int32_t cmd)
{
switch (cmd)
{
case APP_CMD_DESTROY:
//This gets called initially after back.
HandleDestroy();
ANativeActivity_finish( gapp->activity );
break;
case APP_CMD_INIT_WINDOW:
//When returning from a back button suspension, this isn't called.
if( !OGLESStarted )
{
OGLESStarted = 1;
printf( "Got start event\n" );
}
else
{
CNFGSetup( "", -1, -1 );
HandleResume();
}
break;
case APP_CMD_TERM_WINDOW:
//This gets called initially when you click "back"
//This also gets called when you are brought into standby.
//Not sure why - callbacks here seem to break stuff.
if( egl_display != EGL_NO_DISPLAY ) {
eglMakeCurrent( egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT );
if( egl_context != EGL_NO_CONTEXT ) {
eglDestroyContext( egl_display, egl_context );
}
if( egl_surface != EGL_NO_SURFACE ) {
eglDestroySurface( egl_display, egl_surface );
}
eglTerminate( egl_display );
}
egl_context = EGL_NO_CONTEXT;
egl_surface = EGL_NO_SURFACE;
egl_display = EGL_NO_DISPLAY;
#ifdef ANDROID_WANT_WINDOW_TERMINATION
if( HandleWindowTermination ) HandleWindowTermination();
#endif
break;
case APP_CMD_PAUSE:
HandleSuspend();
break;
case APP_CMD_RESUME:
HandleResume();
break;
case APP_CMD_CUSTOM_EVENT:
if( HandleCustomEventCallback ) HandleCustomEventCallback();
break;
default:
printf( "event not handled: %d\n", cmd);
}
}
int __system_property_get(const char* name, char* value);
void android_main(struct android_app* app)
{
int main( int argc, char ** argv );
char mainptr[5] = { 'm', 'a', 'i', 'n', 0 };
char * argv[] = { mainptr, 0 };
{
char sdk_ver_str[92];
int len = __system_property_get("ro.build.version.sdk", sdk_ver_str);
if( len <= 0 )
android_sdk_version = 0;
else
android_sdk_version = atoi(sdk_ver_str);
}
gapp = app;
app->onAppCmd = handle_cmd;
app->onInputEvent = handle_input;
printf( "Starting with Android SDK Version: %d\n", android_sdk_version );
main( 1, argv );
printf( "Main Complete\n" );
}
#ifdef __cplusplus
#define SETUP_FOR_JAVA_CALL \
JNIEnv * env = 0; \
JNIEnv ** envptr = &env; \
JavaVM * jniiptr = gapp->activity->vm; \
jniiptr->AttachCurrentThread( (JNIEnv**)&env, 0 ); \
env = (*envptr);
#define ENVCALL
#define JAVA_CALL_DETACH jniiptr->DetachCurrentThread();
#else
#define SETUP_FOR_JAVA_CALL \
const struct JNINativeInterface * env = (struct JNINativeInterface*)gapp->activity->env; \
const struct JNINativeInterface ** envptr = &env; \
const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm; \
const struct JNIInvokeInterface * jnii = *jniiptr; \
jnii->AttachCurrentThread( jniiptr, &envptr, NULL); \
env = (*envptr);
#define ENVCALL envptr,
#define JAVA_CALL_DETACH jnii->DetachCurrentThread( jniiptr );
#endif
void AndroidMakeFullscreen()
{
//Partially based on https://stackoverflow.com/questions/47507714/how-do-i-enable-full-screen-immersive-mode-for-a-native-activity-ndk-app
SETUP_FOR_JAVA_CALL
//Get android.app.NativeActivity, then get getWindow method handle, returns view.Window type
jclass activityClass = env->FindClass( ENVCALL "android/app/NativeActivity");
jmethodID getWindow = env->GetMethodID( ENVCALL activityClass, "getWindow", "()Landroid/view/Window;");
jobject window = env->CallObjectMethod( ENVCALL gapp->activity->clazz, getWindow);
jclass windowClass = env->FindClass( ENVCALL "android/view/Window");
jmethodID getDecorView = env->GetMethodID( ENVCALL windowClass, "getDecorView", "()Landroid/view/View;");
jobject decorView = env->CallObjectMethod( ENVCALL window, getDecorView);
/*
jclass ClassActivity = env->FindClass( ENVCALL "android/app/Activity" );
const int flag_WindowProp = env->GetStaticIntField( ENVCALL windowClass, env->GetStaticFieldID( ENVCALL windowClass, "FEATURE_NO_TITLE", "I") );
jmethodID requestWindowFeature = env->GetMethodID( ENVCALL ClassActivity, "requestWindowFeature", "(I)Z" );
jobject lNativeActivity = gapp->activity->clazz;
env->CallBooleanMethod( ENVCALL lNativeActivity, requestWindowFeature, flag_WindowProp );
*/
//Get the flag values associated with systemuivisibility
jclass viewClass = env->FindClass( ENVCALL "android/view/View");
const int flagLayoutHideNavigation = env->GetStaticIntField( ENVCALL viewClass, env->GetStaticFieldID( ENVCALL viewClass, "SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION", "I"));
const int flagLayoutFullscreen = env->GetStaticIntField( ENVCALL viewClass, env->GetStaticFieldID( ENVCALL viewClass, "SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN", "I"));
const int flagLowProfile = env->GetStaticIntField( ENVCALL viewClass, env->GetStaticFieldID( ENVCALL viewClass, "SYSTEM_UI_FLAG_LOW_PROFILE", "I"));
const int flagHideNavigation = env->GetStaticIntField( ENVCALL viewClass, env->GetStaticFieldID( ENVCALL viewClass, "SYSTEM_UI_FLAG_HIDE_NAVIGATION", "I"));
const int flagFullscreen = env->GetStaticIntField( ENVCALL viewClass, env->GetStaticFieldID( ENVCALL viewClass, "SYSTEM_UI_FLAG_FULLSCREEN", "I"));
const int flagImmersiveSticky = env->GetStaticIntField( ENVCALL viewClass, env->GetStaticFieldID( ENVCALL viewClass, "SYSTEM_UI_FLAG_IMMERSIVE_STICKY", "I"));
const int flagLayoutStable = env->GetStaticIntField( ENVCALL viewClass, env->GetStaticFieldID( ENVCALL viewClass, "SYSTEM_UI_FLAG_LAYOUT_STABLE", "I"));
jmethodID setSystemUiVisibility = env->GetMethodID( ENVCALL viewClass, "setSystemUiVisibility", "(I)V");
//Call the decorView.setSystemUiVisibility(FLAGS)
env->CallVoidMethod( ENVCALL decorView, setSystemUiVisibility,
(flagLayoutHideNavigation | flagLayoutFullscreen | flagLowProfile | flagHideNavigation | flagFullscreen | flagImmersiveSticky | flagLayoutStable));
//now set some more flags associated with layoutmanager -- note the $ in the class path
//search for api-versions.xml
//https://android.googlesource.com/platform/development/+/refs/tags/android-9.0.0_r48/sdk/api-versions.xml
jclass layoutManagerClass = env->FindClass( ENVCALL "android/view/WindowManager$LayoutParams");
const int flag_WinMan_Fullscreen = env->GetStaticIntField( ENVCALL layoutManagerClass, (env->GetStaticFieldID( ENVCALL layoutManagerClass, "FLAG_FULLSCREEN", "I") ));
const int flag_WinMan_KeepScreenOn = env->GetStaticIntField( ENVCALL layoutManagerClass, (env->GetStaticFieldID( ENVCALL layoutManagerClass, "FLAG_KEEP_SCREEN_ON", "I") ));
const int flag_WinMan_hw_acc = env->GetStaticIntField( ENVCALL layoutManagerClass, (env->GetStaticFieldID( ENVCALL layoutManagerClass, "FLAG_HARDWARE_ACCELERATED", "I") ));
const int flag_WinMan_NoLimits = env->GetStaticIntField( ENVCALL layoutManagerClass, (env->GetStaticFieldID( ENVCALL layoutManagerClass, "FLAG_LAYOUT_NO_LIMITS", "I") ));
// const int flag_WinMan_flag_not_fullscreen = env->GetStaticIntField(layoutManagerClass, (env->GetStaticFieldID(layoutManagerClass, "FLAG_FORCE_NOT_FULLSCREEN", "I") ));
//call window.addFlags(FLAGS)
env->CallVoidMethod( ENVCALL window, (env->GetMethodID (ENVCALL windowClass, "addFlags" , "(I)V")), (flag_WinMan_Fullscreen | flag_WinMan_KeepScreenOn | flag_WinMan_hw_acc | flag_WinMan_NoLimits));
/*
// Seems to have no impact, and doesn't work with older Android versions.
jmethodID setDecorFitsSystemWindows = env->GetMethodID( ENVCALL windowClass, "setDecorFitsSystemWindows", "(Z)V");
env->CallVoidMethod( ENVCALL window, setDecorFitsSystemWindows, JNI_FALSE );
// "Immersive Mode" (Since Android 11+)
jmethodID getWindowInsetsController = env->GetMethodID( ENVCALL viewClass, "getWindowInsetsController", "()Landroid/view/WindowInsetsController;" );
if( getWindowInsetsController )
{
//windowInsetsController.hide(Type.systemBars())
jobject windowInsetsController = env->CallObjectMethod( ENVCALL decorView, getWindowInsetsController );
jclass windowInsetsControllerClass = env->FindClass( ENVCALL "android/view/WindowInsetsController" );
jclass windowInsetsTypeClass = env->FindClass( ENVCALL "android/view/WindowInsets/Type" );
jmethodID typeSystemBars = env->GetStaticMethodID( ENVCALL windowInsetsTypeClass, "systemBars", "()I" );
int systemBarsType = env->CallStaticIntMethod( ENVCALL windowInsetsTypeClass, typeSystemBars );
jmethodID hide = env->GetMethodID( ENVCALL windowInsetsControllerClass, "hide", "(I)V" );
env->CallVoidMethod( ENVCALL windowInsetsController, hide, systemBarsType );
}
*/
JAVA_CALL_DETACH
}
const char* AndroidGetExternalFilesDir()
{
const struct JNINativeInterface * env = 0;
const struct JNINativeInterface ** envptr = &env;
const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm;
const struct JNIInvokeInterface * jnii = *jniiptr;
jnii->AttachCurrentThread( jniiptr, &envptr, NULL);
env = (*envptr);
jclass activityClass = env->FindClass( envptr, "android/app/NativeActivity");
jobject lNativeActivity = gapp->activity->clazz;
jmethodID mid_getExtStorage = env->GetMethodID(envptr,activityClass,"getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;");
jobject obj_File = env->CallObjectMethod(envptr,lNativeActivity, mid_getExtStorage, NULL);
jclass cls_File = env->FindClass(envptr,"java/io/File");
jmethodID mid_getPath = env->GetMethodID(envptr,cls_File, "getPath", "()Ljava/lang/String;");
jstring obj_Path = (jstring) env->CallObjectMethod(envptr,obj_File, mid_getPath);
const char* path = env->GetStringUTFChars(envptr,obj_Path, NULL);
//printf("EXTERNAL PATH = %s\n", path);
env->ReleaseStringUTFChars(envptr,obj_Path, path);
jnii->DetachCurrentThread( jniiptr );
return path;
}
void AndroidDisplayKeyboard(int pShow)
{
//Based on https://stackoverflow.com/questions/5864790/how-to-show-the-soft-keyboard-on-native-activity
jint lFlags = 0;
SETUP_FOR_JAVA_CALL
jclass activityClass = env->FindClass( ENVCALL "android/app/NativeActivity");
// Retrieves NativeActivity.
jobject lNativeActivity = gapp->activity->clazz;
// Retrieves Context.INPUT_METHOD_SERVICE.
jclass ClassContext = env->FindClass( ENVCALL "android/content/Context");
jfieldID FieldINPUT_METHOD_SERVICE = env->GetStaticFieldID( ENVCALL ClassContext, "INPUT_METHOD_SERVICE", "Ljava/lang/String;" );
jobject INPUT_METHOD_SERVICE = env->GetStaticObjectField( ENVCALL ClassContext, FieldINPUT_METHOD_SERVICE );
// Runs getSystemService(Context.INPUT_METHOD_SERVICE).
jclass ClassInputMethodManager = env->FindClass( ENVCALL "android/view/inputmethod/InputMethodManager" );
jmethodID MethodGetSystemService = env->GetMethodID( ENVCALL activityClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
jobject lInputMethodManager = env->CallObjectMethod( ENVCALL lNativeActivity, MethodGetSystemService, INPUT_METHOD_SERVICE);
// Runs getWindow().getDecorView().
jmethodID MethodGetWindow = env->GetMethodID( ENVCALL activityClass, "getWindow", "()Landroid/view/Window;");
jobject lWindow = env->CallObjectMethod( ENVCALL lNativeActivity, MethodGetWindow);
jclass ClassWindow = env->FindClass( ENVCALL "android/view/Window");
jmethodID MethodGetDecorView = env->GetMethodID( ENVCALL ClassWindow, "getDecorView", "()Landroid/view/View;");
jobject lDecorView = env->CallObjectMethod( ENVCALL lWindow, MethodGetDecorView);
if (pShow) {
// Runs lInputMethodManager.showSoftInput(...).
jmethodID MethodShowSoftInput = env->GetMethodID( ENVCALL ClassInputMethodManager, "showSoftInput", "(Landroid/view/View;I)Z");
/*jboolean lResult = */env->CallBooleanMethod( ENVCALL lInputMethodManager, MethodShowSoftInput, lDecorView, lFlags);
} else {
// Runs lWindow.getViewToken()
jclass ClassView = env->FindClass( ENVCALL "android/view/View");
jmethodID MethodGetWindowToken = env->GetMethodID( ENVCALL ClassView, "getWindowToken", "()Landroid/os/IBinder;");
jobject lBinder = env->CallObjectMethod( ENVCALL lDecorView, MethodGetWindowToken);
// lInputMethodManager.hideSoftInput(...).
jmethodID MethodHideSoftInput = env->GetMethodID( ENVCALL ClassInputMethodManager, "hideSoftInputFromWindow", "(Landroid/os/IBinder;I)Z");
/*jboolean lRes = */env->CallBooleanMethod( ENVCALL lInputMethodManager, MethodHideSoftInput, lBinder, lFlags);
}
JAVA_CALL_DETACH
}
int AndroidGetUnicodeChar( int keyCode, int metaState )
{
//https://stackoverflow.com/questions/21124051/receive-complete-android-unicode-input-in-c-c/43871301
int eventType = AKEY_EVENT_ACTION_DOWN;
SETUP_FOR_JAVA_CALL
//jclass activityClass = env->FindClass( envptr, "android/app/NativeActivity");
// Retrieves NativeActivity.
//jobject lNativeActivity = gapp->activity->clazz;
jclass class_key_event = env->FindClass( ENVCALL "android/view/KeyEvent");
int unicodeKey;
jmethodID method_get_unicode_char = env->GetMethodID( ENVCALL class_key_event, "getUnicodeChar", "(I)I");
jmethodID eventConstructor = env->GetMethodID( ENVCALL class_key_event, "<init>", "(II)V");
jobject eventObj = env->NewObject( ENVCALL class_key_event, eventConstructor, eventType, keyCode);
unicodeKey = env->CallIntMethod( ENVCALL eventObj, method_get_unicode_char, metaState );
// Finished with the JVM.
JAVA_CALL_DETACH
//printf("Unicode key is: %d", unicodeKey);
return unicodeKey;
}
//Based on: https://stackoverflow.com/questions/41820039/jstringjni-to-stdstringc-with-utf8-characters
#ifdef __cplusplus
jstring android_permission_name(JNIEnv ** envptr, const char* perm_name)
#else
jstring android_permission_name(const struct JNINativeInterface ** envptr, const char* perm_name)
#endif
{
// nested class permission in class android.Manifest,
// hence android 'slash' Manifest 'dollar' permission
#ifdef __cplusplus
JNIEnv * env = *envptr;
#else
const struct JNINativeInterface * env = *envptr;
#endif
jclass ClassManifestpermission = env->FindClass( ENVCALL "android/Manifest$permission");
jfieldID lid_PERM = env->GetStaticFieldID( ENVCALL ClassManifestpermission, perm_name, "Ljava/lang/String;" );
jstring ls_PERM = (jstring)(env->GetStaticObjectField( ENVCALL ClassManifestpermission, lid_PERM ));
return ls_PERM;
}
/**
* \brief Tests whether a permission is granted.
* \param[in] app a pointer to the android app.
* \param[in] perm_name the name of the permission, e.g.,
* "READ_EXTERNAL_STORAGE", "WRITE_EXTERNAL_STORAGE".
* \retval true if the permission is granted.
* \retval false otherwise.
* \note Requires Android API level 23 (Marshmallow, May 2015)
*/
int AndroidHasPermissions( const char* perm_name)
{
struct android_app* app = gapp;
SETUP_FOR_JAVA_CALL
if( android_sdk_version < 23 )
{
printf( "Android SDK version %d does not support AndroidHasPermissions\n", android_sdk_version );
return 1;
}
int result = 0;
jstring ls_PERM = android_permission_name(envptr, perm_name);
jint PERMISSION_GRANTED = (-1);
{
jclass ClassPackageManager = env->FindClass( ENVCALL "android/content/pm/PackageManager" );
jfieldID lid_PERMISSION_GRANTED = env->GetStaticFieldID( ENVCALL ClassPackageManager, "PERMISSION_GRANTED", "I" );
PERMISSION_GRANTED = env->GetStaticIntField( ENVCALL ClassPackageManager, lid_PERMISSION_GRANTED );
}
{
jobject activity = app->activity->clazz;
jclass ClassContext = env->FindClass( ENVCALL "android/content/Context" );
jmethodID MethodcheckSelfPermission = env->GetMethodID( ENVCALL ClassContext, "checkSelfPermission", "(Ljava/lang/String;)I" );
jint int_result = env->CallIntMethod( ENVCALL activity, MethodcheckSelfPermission, ls_PERM );
result = (int_result == PERMISSION_GRANTED);
}
JAVA_CALL_DETACH
return result;
}
/**
* \brief Query file permissions.
* \details This opens the system dialog that lets the user
* grant (or deny) the permission.
* \param[in] app a pointer to the android app.
* \note Requires Android API level 23 (Marshmallow, May 2015)
*/
void AndroidRequestAppPermissions(const char * perm)
{
if( android_sdk_version < 23 )
{
printf( "Android SDK version %d does not support AndroidRequestAppPermissions\n",android_sdk_version );
return;
}
struct android_app* app = gapp;
SETUP_FOR_JAVA_CALL
jobject activity = app->activity->clazz;
jobjectArray perm_array = env->NewObjectArray( ENVCALL 1, env->FindClass( ENVCALL "java/lang/String"), env->NewStringUTF( ENVCALL "" ) );
env->SetObjectArrayElement( ENVCALL perm_array, 0, android_permission_name(envptr, perm ) );
jclass ClassActivity = env->FindClass( ENVCALL "android/app/Activity" );
jmethodID MethodrequestPermissions = env->GetMethodID( ENVCALL ClassActivity, "requestPermissions", "([Ljava/lang/String;I)V" );
// Last arg (0) is just for the callback (that I do not use)
env->CallVoidMethod( ENVCALL activity, MethodrequestPermissions, perm_array, 0 );
JAVA_CALL_DETACH
}
/* Example:
int hasperm = android_has_permission( "RECORD_AUDIO" );
if( !hasperm )
{
android_request_app_permissions( "RECORD_AUDIO" );
}
*/
void AndroidSendToBack( int param )
{
struct android_app* app = gapp;
SETUP_FOR_JAVA_CALL
jobject activity = app->activity->clazz;
//_glfmCallJavaMethodWithArgs(jni, gapp->activity->clazz, "moveTaskToBack", "(Z)Z", Boolean, false);
jclass ClassActivity = env->FindClass( ENVCALL "android/app/Activity" );
jmethodID MethodmoveTaskToBack = env->GetMethodID( ENVCALL ClassActivity, "moveTaskToBack", "(Z)Z" );
env->CallBooleanMethod( ENVCALL activity, MethodmoveTaskToBack, param );
JAVA_CALL_DETACH
}
#endif

View file

@ -0,0 +1,137 @@
//Copyright (c) 2011, 2017, 2018, 2020 <>< Charles Lohr - Under the MIT/x11 or NewBSD License you choose.
//This driver cannot create an OpenGL Surface, but can be used for computing for background tasks.
//NOTE: This is a truly incomplete driver - if no EGL surface is available, it does not support direct buffer rendering.
//Additionally no input is connected.
#include "CNFG.h"
#include <stdio.h>
#include <stdlib.h>
#include <GLES3/gl3.h>
#include <GLES3/gl32.h>
#include <EGL/egl.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <GLES2/gl2ext.h>
static const EGLint configAttribs[] = {
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_NONE
};
EGLint context_attribs[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
static int pbufferWidth = 0;
static int pbufferHeight = 0;
static EGLint pbufferAttribs[] = {
EGL_WIDTH, 0,
EGL_HEIGHT, 0,
EGL_NONE,
};
EGLDisplay eglDpy = 0;
EGLContext eglCtx = 0;
EGLSurface eglSurf = 0;
void CNFGGetDimensions( short * x, short * y )
{
*x = pbufferWidth;
*y = pbufferHeight;
}
void CNFGChangeWindowTitle( const char * WindowName )
{
}
void CNFGSetupFullscreen( const char * WindowName, int screen_no )
{
//Fullscreen is meaningless for this driver, since it doesn't really open a window.
CNFGSetup( WindowName, 1024, 1024 );
}
void CNFGTearDown()
{
if( eglDpy )
{
eglTerminate( eglDpy );
}
//Unimplemented.
}
int CNFGSetup( const char * WindowName, int w, int h )
{
eglDpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
atexit( CNFGTearDown );
printf( "EGL Display: %p\n", eglDpy );
pbufferAttribs[1] = pbufferWidth = w;
pbufferAttribs[3] = pbufferHeight = h;
EGLint major, minor;
eglInitialize(eglDpy, &major, &minor);
EGLint numConfigs=0;
EGLConfig eglCfg=NULL;
eglChooseConfig(eglDpy, configAttribs, 0, 0, &numConfigs); //this gets number of configs
if (numConfigs) {
eglChooseConfig(eglDpy, configAttribs, &eglCfg, 1, &numConfigs);
printf( " EGL config found\n" );
} else {
printf( " Error could not find a valid config avail.. \n" );
}
printf( "EGL Major Minor: %d %d\n", major, minor );
eglBindAPI(EGL_OPENGL_API);
eglCtx = eglCreateContext(eglDpy, eglCfg, EGL_NO_CONTEXT, context_attribs);
int err = eglGetError(); if(err != EGL_SUCCESS) { printf("1. Error %d\n", err); }
printf( "EGL Got context: %p\n", eglCtx );
if( w > 0 && h > 0 )
{
eglSurf = eglCreatePbufferSurface(eglDpy, eglCfg, pbufferAttribs);
eglMakeCurrent(eglDpy, eglSurf, eglSurf, eglCtx);
printf( "EGL Current, with surface %p\n", eglSurf );
//Actually have a surface. Need to allocate it.
EGLint surfwid;
EGLint surfht;
eglQuerySurface(eglDpy, eglSurf, EGL_WIDTH, &surfwid);
eglQuerySurface(eglDpy, eglSurf, EGL_HEIGHT, &surfht);
printf("Window dimensions: %d x %d\n", surfwid, surfht);
}
else
{
eglMakeCurrent(eglDpy, EGL_NO_SURFACE, EGL_NO_SURFACE, eglCtx);
printf( "EGL Current, no surface.\n" );
}
return 0;
}
int CNFGHandleInput()
{
//Stubbed (No input)
return 1;
}
void CNFGSetVSync( int vson )
{
//No-op
}
void CNFGSwapBuffers()
{
//No-op
}

View file

@ -0,0 +1,959 @@
/*
Copyright (c) 2010-2021 <>< Charles Lohr, and several others!
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef _CNFG_C
#define _CNFG_C
#include "CNFG.h"
#ifndef CNFGHTTPSERVERONLY
#ifdef _CNFG_FANCYFONT
#include "TextTool/FontData.h"
#endif
int CNFGPenX, CNFGPenY;
uint32_t CNFGBGColor;
uint32_t CNFGLastColor;
//uint32_t CNFGDialogColor; //background for boxes [DEPRECATED]
// The following two arrays are generated by Fonter/fonter.cpp
const unsigned short RawdrawFontCharMap[256] = {
65535, 0, 8, 16, 24, 31, 41, 50, 51, 65535, 65535, 57, 66, 65535, 75, 83,
92, 96, 100, 108, 114, 123, 132, 137, 147, 152, 158, 163, 169, 172, 178, 182,
65535, 186, 189, 193, 201, 209, 217, 226, 228, 232, 236, 244, 248, 250, 252, 253,
255, 261, 266, 272, 278, 283, 289, 295, 300, 309, 316, 318, 321, 324, 328, 331,
337, 345, 352, 362, 368, 375, 382, 388, 396, 402, 408, 413, 422, 425, 430, 435,
442, 449, 458, 466, 472, 476, 480, 485, 492, 500, 507, 512, 516, 518, 522, 525,
527, 529, 536, 541, 546, 551, 557, 564, 572, 578, 581, 586, 593, 595, 604, 610,
615, 621, 627, 632, 638, 642, 648, 653, 660, 664, 670, 674, 680, 684, 690, 694,
65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535,
65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535,
700, 703, 711, 718, 731, 740, 744, 754, 756, 760, 766, 772, 775, 777, 785, 787,
792, 798, 803, 811, 813, 820, 827, 828, 831, 833, 838, 844, 853, 862, 874, 880,
889, 898, 908, 919, 928, 939, 951, 960, 969, 978, 988, 997, 1005, 1013, 1022, 1030,
1039, 1047, 1054, 1061, 1070, 1079, 1086, 1090, 1099, 1105, 1111, 1118, 1124, 1133, 1140, 1150,
1159, 1168, 1178, 1189, 1198, 1209, 1222, 1231, 1239, 1247, 1256, 1264, 1268, 1272, 1277, 1281,
1290, 1300, 1307, 1314, 1322, 1331, 1338, 1342, 1349, 1357, 1365, 1374, 1382, 1390, 1397, 65535, };
const unsigned char RawdrawFontCharData[1405] = {
0x00, 0x09, 0x20, 0x29, 0x03, 0x23, 0x14, 0x8b, 0x00, 0x09, 0x20, 0x29, 0x04, 0x24, 0x13, 0x8c,
0x01, 0x21, 0x23, 0x14, 0x03, 0x09, 0x11, 0x9a, 0x11, 0x22, 0x23, 0x14, 0x03, 0x02, 0x99, 0x01,
0x21, 0x23, 0x09, 0x03, 0x29, 0x03, 0x09, 0x12, 0x9c, 0x03, 0x2b, 0x13, 0x1c, 0x23, 0x22, 0x11,
0x02, 0x8b, 0x9a, 0x1a, 0x01, 0x21, 0x23, 0x03, 0x89, 0x03, 0x21, 0x2a, 0x21, 0x19, 0x03, 0x14,
0x23, 0x9a, 0x01, 0x10, 0x21, 0x12, 0x09, 0x12, 0x1c, 0x03, 0xab, 0x02, 0x03, 0x1b, 0x02, 0x1a,
0x13, 0x10, 0xa9, 0x01, 0x2b, 0x03, 0x29, 0x02, 0x11, 0x22, 0x13, 0x8a, 0x00, 0x22, 0x04, 0x88,
0x20, 0x02, 0x24, 0xa8, 0x01, 0x10, 0x29, 0x10, 0x14, 0x0b, 0x14, 0xab, 0x00, 0x0b, 0x0c, 0x20,
0x2b, 0xac, 0x00, 0x28, 0x00, 0x02, 0x2a, 0x10, 0x1c, 0x20, 0xac, 0x01, 0x21, 0x23, 0x03, 0x09,
0x20, 0x10, 0x14, 0x8c, 0x03, 0x23, 0x24, 0x04, 0x8b, 0x01, 0x10, 0x29, 0x10, 0x14, 0x0b, 0x14,
0x2b, 0x04, 0xac, 0x01, 0x18, 0x21, 0x10, 0x9c, 0x03, 0x1c, 0x23, 0x1c, 0x10, 0x9c, 0x02, 0x22,
0x19, 0x22, 0x9b, 0x02, 0x2a, 0x02, 0x19, 0x02, 0x9b, 0x01, 0x02, 0xaa, 0x02, 0x22, 0x11, 0x02,
0x13, 0xaa, 0x11, 0x22, 0x02, 0x99, 0x02, 0x13, 0x22, 0x8a, 0x10, 0x1b, 0x9c, 0x10, 0x09, 0x20,
0x99, 0x10, 0x1c, 0x20, 0x2c, 0x01, 0x29, 0x03, 0xab, 0x21, 0x10, 0x01, 0x23, 0x14, 0x0b, 0x10,
0x9c, 0x00, 0x09, 0x23, 0x2c, 0x04, 0x03, 0x21, 0xa8, 0x21, 0x10, 0x01, 0x12, 0x03, 0x14, 0x2b,
0x02, 0xac, 0x10, 0x99, 0x10, 0x01, 0x03, 0x9c, 0x10, 0x21, 0x23, 0x9c, 0x01, 0x2b, 0x11, 0x1b,
0x21, 0x0b, 0x02, 0xaa, 0x02, 0x2a, 0x11, 0x9b, 0x04, 0x9b, 0x02, 0xaa, 0x9c, 0x03, 0xa9, 0x00,
0x20, 0x24, 0x04, 0x08, 0x9a, 0x01, 0x10, 0x1c, 0x04, 0xac, 0x01, 0x10, 0x21, 0x22, 0x04, 0xac,
0x00, 0x20, 0x24, 0x0c, 0x12, 0xaa, 0x00, 0x02, 0x2a, 0x20, 0xac, 0x20, 0x00, 0x02, 0x22, 0x24,
0x8c, 0x20, 0x02, 0x22, 0x24, 0x04, 0x8a, 0x00, 0x20, 0x21, 0x12, 0x9c, 0x00, 0x0c, 0x00, 0x20,
0x2c, 0x04, 0x2c, 0x02, 0xaa, 0x00, 0x02, 0x22, 0x20, 0x08, 0x22, 0x8c, 0x19, 0x9b, 0x19, 0x13,
0x8c, 0x20, 0x02, 0xac, 0x01, 0x29, 0x03, 0xab, 0x00, 0x22, 0x8c, 0x01, 0x10, 0x21, 0x12, 0x1b,
0x9c, 0x21, 0x01, 0x04, 0x24, 0x22, 0x12, 0x13, 0xab, 0x04, 0x01, 0x10, 0x21, 0x2c, 0x02, 0xaa,
0x00, 0x04, 0x14, 0x23, 0x12, 0x0a, 0x12, 0x21, 0x10, 0x88, 0x23, 0x14, 0x03, 0x01, 0x10, 0xa9,
0x00, 0x10, 0x21, 0x23, 0x14, 0x04, 0x88, 0x00, 0x04, 0x2c, 0x00, 0x28, 0x02, 0x9a, 0x00, 0x0c,
0x00, 0x28, 0x02, 0x9a, 0x21, 0x10, 0x01, 0x03, 0x14, 0x23, 0x22, 0x9a, 0x00, 0x0c, 0x20, 0x2c,
0x02, 0xaa, 0x00, 0x28, 0x10, 0x1c, 0x04, 0xac, 0x00, 0x20, 0x23, 0x14, 0x8b, 0x00, 0x0c, 0x02,
0x12, 0x21, 0x28, 0x12, 0x23, 0xac, 0x00, 0x04, 0xac, 0x04, 0x00, 0x11, 0x20, 0xac, 0x04, 0x00,
0x2a, 0x20, 0xac, 0x01, 0x10, 0x21, 0x23, 0x14, 0x03, 0x89, 0x00, 0x0c, 0x00, 0x10, 0x21, 0x12,
0x8a, 0x01, 0x10, 0x21, 0x23, 0x14, 0x03, 0x09, 0x04, 0x9b, 0x00, 0x0c, 0x00, 0x10, 0x21, 0x12,
0x02, 0xac, 0x21, 0x10, 0x01, 0x23, 0x14, 0x8b, 0x00, 0x28, 0x10, 0x9c, 0x00, 0x04, 0x24, 0xa8,
0x00, 0x03, 0x14, 0x23, 0xa8, 0x00, 0x04, 0x2c, 0x14, 0x1b, 0x24, 0xa8, 0x00, 0x01, 0x23, 0x2c,
0x04, 0x03, 0x21, 0xa8, 0x00, 0x01, 0x12, 0x1c, 0x12, 0x21, 0xa8, 0x00, 0x20, 0x02, 0x04, 0xac,
0x10, 0x00, 0x04, 0x9c, 0x01, 0xab, 0x10, 0x20, 0x24, 0x9c, 0x01, 0x10, 0xa9, 0x04, 0xac, 0x00,
0x99, 0x02, 0x04, 0x24, 0x2a, 0x23, 0x12, 0x8a, 0x00, 0x04, 0x24, 0x22, 0x8a, 0x24, 0x04, 0x03,
0x12, 0xaa, 0x20, 0x24, 0x04, 0x02, 0xaa, 0x24, 0x04, 0x02, 0x22, 0x23, 0x9b, 0x04, 0x09, 0x02,
0x1a, 0x01, 0x10, 0xa9, 0x23, 0x12, 0x03, 0x14, 0x23, 0x24, 0x15, 0x8c, 0x00, 0x0c, 0x03, 0x12,
0x23, 0xac, 0x19, 0x12, 0x9c, 0x2a, 0x23, 0x24, 0x15, 0x8c, 0x00, 0x0c, 0x03, 0x13, 0x2a, 0x13,
0xac, 0x10, 0x9c, 0x02, 0x0c, 0x02, 0x1b, 0x12, 0x1c, 0x12, 0x23, 0xac, 0x02, 0x0c, 0x03, 0x12,
0x23, 0xac, 0x02, 0x22, 0x24, 0x04, 0x8a, 0x02, 0x0d, 0x04, 0x24, 0x22, 0x8a, 0x02, 0x04, 0x2c,
0x25, 0x22, 0x8a, 0x02, 0x0c, 0x03, 0x12, 0xaa, 0x22, 0x02, 0x03, 0x23, 0x24, 0x8c, 0x11, 0x1c,
0x02, 0xaa, 0x02, 0x04, 0x14, 0x2b, 0x24, 0xaa, 0x02, 0x03, 0x14, 0x23, 0xaa, 0x02, 0x03, 0x14,
0x1a, 0x13, 0x24, 0xaa, 0x02, 0x2c, 0x04, 0xaa, 0x02, 0x03, 0x1c, 0x22, 0x23, 0x8d, 0x02, 0x22,
0x04, 0xac, 0x20, 0x10, 0x14, 0x2c, 0x12, 0x8a, 0x10, 0x19, 0x13, 0x9c, 0x00, 0x10, 0x14, 0x0c,
0x12, 0xaa, 0x01, 0x10, 0x11, 0xa8, 0x03, 0x04, 0x24, 0x23, 0x12, 0x8b, 0x18, 0x11, 0x9c, 0x21,
0x10, 0x01, 0x02, 0x13, 0x2a, 0x10, 0x9b, 0x11, 0x00, 0x04, 0x24, 0x2b, 0x02, 0x9a, 0x01, 0x0a,
0x11, 0x29, 0x22, 0x2b, 0x03, 0x1b, 0x02, 0x11, 0x22, 0x13, 0x8a, 0x00, 0x11, 0x28, 0x11, 0x1c,
0x02, 0x2a, 0x03, 0xab, 0x10, 0x1a, 0x13, 0x9d, 0x20, 0x00, 0x02, 0x11, 0x2a, 0x02, 0x13, 0x22,
0x24, 0x8c, 0x08, 0xa8, 0x20, 0x10, 0x11, 0xa9, 0x10, 0x29, 0x20, 0x21, 0x11, 0x98, 0x11, 0x02,
0x1b, 0x21, 0x12, 0xab, 0x01, 0x21, 0xaa, 0x12, 0xaa, 0x10, 0x20, 0x21, 0x19, 0x12, 0x18, 0x11,
0xaa, 0x00, 0xa8, 0x01, 0x10, 0x21, 0x12, 0x89, 0x02, 0x2a, 0x11, 0x1b, 0x03, 0xab, 0x01, 0x10,
0x21, 0x03, 0xab, 0x01, 0x10, 0x21, 0x12, 0x0a, 0x12, 0x23, 0x8b, 0x11, 0xa8, 0x02, 0x0d, 0x04,
0x14, 0x2b, 0x22, 0xac, 0x14, 0x10, 0x01, 0x1a, 0x10, 0x20, 0xac, 0x9a, 0x14, 0x15, 0x8d, 0x20,
0xa9, 0x10, 0x20, 0x21, 0x11, 0x98, 0x01, 0x12, 0x0b, 0x11, 0x22, 0x9b, 0x00, 0x09, 0x02, 0x28,
0x12, 0x13, 0x2b, 0x22, 0xac, 0x00, 0x09, 0x02, 0x28, 0x12, 0x22, 0x13, 0x14, 0xac, 0x00, 0x10,
0x11, 0x09, 0x11, 0x02, 0x28, 0x12, 0x13, 0x2b, 0x22, 0xac, 0x18, 0x11, 0x12, 0x03, 0x14, 0xab,
0x04, 0x02, 0x11, 0x22, 0x2c, 0x03, 0x2b, 0x10, 0xa9, 0x04, 0x02, 0x11, 0x22, 0x2c, 0x03, 0x2b,
0x01, 0x98, 0x04, 0x02, 0x11, 0x22, 0x2c, 0x03, 0x2b, 0x01, 0x10, 0xa9, 0x04, 0x02, 0x11, 0x22,
0x2c, 0x03, 0x2b, 0x01, 0x10, 0x11, 0xa8, 0x04, 0x02, 0x11, 0x22, 0x2c, 0x03, 0x2b, 0x08, 0xa8,
0x04, 0x02, 0x11, 0x22, 0x2c, 0x03, 0x2b, 0x00, 0x20, 0x11, 0x88, 0x00, 0x0c, 0x02, 0x2a, 0x00,
0x19, 0x10, 0x1c, 0x10, 0x28, 0x14, 0xac, 0x23, 0x14, 0x03, 0x01, 0x10, 0x29, 0x14, 0x15, 0x8d,
0x02, 0x2a, 0x02, 0x04, 0x2c, 0x03, 0x1b, 0x00, 0x99, 0x02, 0x2a, 0x02, 0x04, 0x2c, 0x03, 0x1b,
0x11, 0xa8, 0x02, 0x2a, 0x02, 0x04, 0x2c, 0x03, 0x1b, 0x01, 0x10, 0xa9, 0x02, 0x2a, 0x02, 0x04,
0x2c, 0x03, 0x1b, 0x08, 0xa8, 0x02, 0x2a, 0x12, 0x1c, 0x04, 0x2c, 0x00, 0x99, 0x02, 0x2a, 0x12,
0x1c, 0x04, 0x2c, 0x11, 0xa8, 0x02, 0x2a, 0x12, 0x1c, 0x04, 0x2c, 0x01, 0x10, 0xa9, 0x02, 0x2a,
0x12, 0x1c, 0x04, 0x2c, 0x28, 0x88, 0x00, 0x10, 0x21, 0x23, 0x14, 0x04, 0x08, 0x02, 0x9a, 0x04,
0x02, 0x24, 0x2a, 0x01, 0x10, 0x11, 0xa8, 0x02, 0x22, 0x24, 0x04, 0x0a, 0x00, 0x99, 0x02, 0x22,
0x24, 0x04, 0x0a, 0x11, 0xa8, 0x02, 0x22, 0x24, 0x04, 0x0a, 0x11, 0x28, 0x00, 0x99, 0x02, 0x22,
0x24, 0x04, 0x0a, 0x01, 0x10, 0x11, 0xa8, 0x01, 0x21, 0x24, 0x04, 0x09, 0x08, 0xa8, 0x01, 0x2b,
0x03, 0xa9, 0x01, 0x10, 0x21, 0x23, 0x14, 0x03, 0x09, 0x03, 0xa9, 0x01, 0x04, 0x24, 0x29, 0x11,
0xa8, 0x01, 0x04, 0x24, 0x29, 0x00, 0x99, 0x02, 0x04, 0x24, 0x2a, 0x01, 0x10, 0xa9, 0x01, 0x04,
0x24, 0x29, 0x08, 0xa8, 0x01, 0x02, 0x13, 0x1c, 0x13, 0x22, 0x29, 0x11, 0xa8, 0x00, 0x0c, 0x01,
0x11, 0x22, 0x13, 0x8b, 0x00, 0x0d, 0x00, 0x10, 0x21, 0x1a, 0x02, 0x22, 0x24, 0x8c, 0x02, 0x04,
0x24, 0x2a, 0x23, 0x12, 0x0a, 0x00, 0x99, 0x02, 0x04, 0x24, 0x2a, 0x23, 0x12, 0x0a, 0x11, 0xa8,
0x02, 0x04, 0x24, 0x2a, 0x23, 0x12, 0x0a, 0x01, 0x10, 0xa9, 0x02, 0x04, 0x24, 0x2a, 0x23, 0x12,
0x0a, 0x01, 0x10, 0x11, 0xa8, 0x02, 0x04, 0x24, 0x2a, 0x23, 0x12, 0x0a, 0x09, 0xa9, 0x02, 0x04,
0x24, 0x2a, 0x23, 0x12, 0x0a, 0x01, 0x10, 0x21, 0x89, 0x02, 0x1b, 0x02, 0x04, 0x2c, 0x12, 0x1c,
0x12, 0x2a, 0x13, 0x2b, 0x22, 0xab, 0x03, 0x04, 0x2c, 0x03, 0x12, 0x2a, 0x14, 0x15, 0x8d, 0x24,
0x04, 0x02, 0x22, 0x23, 0x1b, 0x00, 0x99, 0x24, 0x04, 0x02, 0x22, 0x23, 0x1b, 0x11, 0xa8, 0x24,
0x04, 0x02, 0x22, 0x23, 0x1b, 0x01, 0x10, 0xa9, 0x24, 0x04, 0x02, 0x22, 0x23, 0x1b, 0x09, 0xa9,
0x12, 0x1c, 0x00, 0x99, 0x12, 0x1c, 0x11, 0xa8, 0x12, 0x1c, 0x01, 0x10, 0xa9, 0x12, 0x1c, 0x09,
0xa9, 0x00, 0x2a, 0x11, 0x28, 0x02, 0x22, 0x24, 0x04, 0x8a, 0x02, 0x0c, 0x03, 0x12, 0x23, 0x2c,
0x01, 0x10, 0x11, 0xa8, 0x02, 0x04, 0x24, 0x22, 0x0a, 0x00, 0x99, 0x02, 0x04, 0x24, 0x22, 0x0a,
0x11, 0xa8, 0x02, 0x04, 0x24, 0x22, 0x0a, 0x01, 0x10, 0xa9, 0x02, 0x04, 0x24, 0x22, 0x0a, 0x01,
0x10, 0x11, 0xa8, 0x02, 0x04, 0x24, 0x22, 0x0a, 0x09, 0xa9, 0x19, 0x02, 0x2a, 0x9b, 0x02, 0x04,
0x24, 0x22, 0x0a, 0x04, 0xaa, 0x02, 0x04, 0x14, 0x2b, 0x24, 0x2a, 0x00, 0x99, 0x02, 0x04, 0x14,
0x2b, 0x24, 0x2a, 0x11, 0xa8, 0x02, 0x04, 0x14, 0x2b, 0x24, 0x2a, 0x01, 0x10, 0xa9, 0x02, 0x04,
0x14, 0x2b, 0x24, 0x2a, 0x09, 0xa9, 0x02, 0x03, 0x1c, 0x22, 0x23, 0x0d, 0x11, 0xa8, 0x00, 0x0c,
0x02, 0x11, 0x22, 0x13, 0x8a, 0x02, 0x03, 0x1c, 0x22, 0x23, 0x0d, 0x09, 0xa9, };
//Set this if you are only using CNFG to create an OpenGL context.
#ifndef CNFGCONTEXTONLY
uint32_t CNFGDialogColor;
void CNFGDrawBox( short x1, short y1, short x2, short y2 )
{
uint32_t lc = CNFGLastColor;
CNFGColor( CNFGDialogColor );
CNFGTackRectangle( x1, y1, x2, y2 );
CNFGColor( lc );
CNFGTackSegment( x1, y1, x2, y1 );
CNFGTackSegment( x2, y1, x2, y2 );
CNFGTackSegment( x2, y2, x1, y2 );
CNFGTackSegment( x1, y2, x1, y1 );
}
void CNFGDrawText( const char * text, short scale )
{
const unsigned char * lmap;
float iox = (float)CNFGPenX; //x offset
float ioy = (float)CNFGPenY; //y offset
int drawPlace = 0;
unsigned short index;
int bQuit = 0;
while( text[drawPlace] )
{
unsigned char c = text[drawPlace];
switch( c )
{
case 9: // tab
iox += 12 * scale;
break;
case 10: // linefeed
iox = (float)CNFGPenX;
ioy += 6 * scale;
break;
default:
index = RawdrawFontCharMap[c];
if( index == 65535 )
{
iox += 3 * scale;
break;
}
lmap = &RawdrawFontCharData[index];
short penx = 0, peny = 0;
unsigned char start_seg = 1;
do
{
unsigned char data = (*(lmap++));
short x1 = (short)(((data >> 4) & 0x07)*scale + iox);
short y1 = (short)((data & 0x07)*scale + ioy);
if( start_seg )
{
penx = x1;
peny = y1;
start_seg = 0;
if( data & 0x08 )
CNFGTackPixel( x1, y1 );
}
else
{
CNFGTackSegment( penx, peny, x1, y1 );
penx = x1;
peny = y1;
}
if( data & 0x08 ) start_seg = 1;
bQuit = data & 0x80;
} while( !bQuit );
iox += 3 * scale;
}
drawPlace++;
}
}
#ifndef FONT_CREATION_TOOL
#ifdef _CNFG_FANCYFONT
void CNFGDrawNiceText(const char* text, short scale)
{
const unsigned char* lmap;
float iox = (float)CNFGPenX; //x offset
float ioy = (float)CNFGPenY; //y offset
int place = 0;
unsigned short index;
int bQuit = 0;
int segmentEnd = 0;
while (text[place]) {
unsigned char c = text[place];
switch (c)
{
case 9: // tab
iox += 16 * scale;
break;
case 10: // linefeed
iox = (float)CNFGPenX;
ioy += 6 * scale;
break;
default:
index = CharIndex[c];
if (index == 0) {
iox += 4 * scale;
break;
}
lmap = &FontData[index];
short charWidth = ((*lmap) & 0xE0) >> 5; //0b11100000
short xbase = ((*lmap) & 0x18) >> 3; //0b00011000
short ybase = (*lmap) & 0x07; //0b00000111
lmap++;
do {
int x1 = ((((*lmap) & 0x38) >> 3) * scale + iox + xbase * scale); //0b00111000
int y1 = (((*lmap) & 0x07) * scale + ioy + ybase * scale);
segmentEnd = *lmap & 0x40;
int x2 = 0;
int y2 = 0;
lmap++;
if (segmentEnd) {
x2 = x1;
y2 = y1;
}
else {
x2 = ((((*lmap) & 0x38) >> 3) * scale + iox + xbase * scale);
y2 = (((*lmap) & 0x07) * scale + ioy + ybase * scale);
}
CNFGTackSegment(x1, y1, x2, y2);
bQuit = *(lmap - 1) & 0x80;
} while (!bQuit);
iox += (charWidth + 2) * scale;
//iox += 8 * scale;
}
place++;
}
}
#endif
#endif
void CNFGGetTextExtents( const char * text, int * w, int * h, int textsize )
{
int charsx = 0;
int charsy = 1;
int charsline = 0;
const char * s;
for( s = text; *s; s++ )
{
if( *s == '\n' )
{
charsline = 0;
if( *(s+1) )
charsy++;
}
else
{
charsline++;
if( charsline > charsx )
charsx = charsline;
}
}
*w = charsx * textsize * 3-1*textsize;
*h = charsy * textsize * 6;
}
#if defined( CNFG_BATCH )
//This is the path by which we convert rawdraw functionality
//into nice batched triangle streams.
//Just FYI we use floats for geometry instead of shorts becase it is harder
//to triangularize a diagonal line int triangles with shorts and have it look good.
void CNFGEmitBackendTriangles( const float * fv, const uint32_t * col, int nr_verts );
//If on WASM, sqrtf is implied. On other platforms, need sqrtf from math.h
#ifdef __wasm__
float sqrtf( float f );
#define cnfg_sqrtf sqrtf
#elif defined( __TINYC__ ) && defined( WIN32 )
#define cnfg_sqrtf sqrt
#else
#define cnfg_sqrtf sqrtf
#include <math.h>
#endif
//Geometry batching system - so we can batch geometry and deliver it all at once.
float CNFGVertDataV[CNFG_BATCH*3];
uint32_t CNFGVertDataC[CNFG_BATCH];
int CNFGVertPlace;
static float wgl_last_width_over_2 = .5f;
static void EmitQuad( float cx0, float cy0, float cx1, float cy1, float cx2, float cy2, float cx3, float cy3 )
{
//Because quads are really useful, but it's best to keep them all triangles if possible.
//This lets us draw arbitrary quads.
if( CNFGVertPlace >= CNFG_BATCH-6 ) CNFGFlushRender();
float * fv = &CNFGVertDataV[CNFGVertPlace*3];
fv[0] = cx0; fv[1] = cy0;
fv[3] = cx1; fv[4] = cy1;
fv[6] = cx2; fv[7] = cy2;
fv[9] = cx2; fv[10] = cy2;
fv[12] = cx1; fv[13] = cy1;
fv[15] = cx3; fv[16] = cy3;
uint32_t * col = &CNFGVertDataC[CNFGVertPlace];
uint32_t color = CNFGLastColor;
col[0] = color; col[1] = color; col[2] = color; col[3] = color; col[4] = color; col[5] = color;
CNFGVertPlace += 6;
}
#if !defined( CNFGRASTERIZER ) && !defined( CNFGHTTP )
void CNFGTackPixel( short x1, short y1 )
{
x1++; y1++;
const float l2 = wgl_last_width_over_2;
const float l2u = wgl_last_width_over_2+0.5f;
EmitQuad( x1-l2u, y1-l2u, x1+l2, y1-l2u, x1-l2u, y1+l2, x1+l2, y1+l2 );
}
void CNFGTackSegment( short x1, short y1, short x2, short y2 )
{
float ix1 = x1;
float iy1 = y1;
float ix2 = x2;
float iy2 = y2;
float dx = ix2-ix1;
float dy = iy2-iy1;
float imag = 1.f/(float)(cnfg_sqrtf(dx*dx+dy*dy));
dx *= imag;
dy *= imag;
float orthox = dy*wgl_last_width_over_2;
float orthoy =-dx*wgl_last_width_over_2;
ix2 += dx/2 + 0.5f;
iy2 += dy/2 + 0.5f;
ix1 -= dx/2 - 0.5f;
iy1 -= dy/2 - 0.5f;
//This logic is incorrect. XXX FIXME.
EmitQuad( (ix1 - orthox), (iy1 - orthoy), (ix1 + orthox), (iy1 + orthoy), (ix2 - orthox), (iy2 - orthoy), ( ix2 + orthox), ( iy2 + orthoy) );
}
void CNFGTackRectangle( short x1, short y1, short x2, short y2 )
{
float ix1 = x1;
float iy1 = y1;
float ix2 = x2;
float iy2 = y2;
EmitQuad( ix1,iy1,ix2,iy1,ix1,iy2,ix2,iy2 );
}
void CNFGTackPoly( RDPoint * points, int verts )
{
int i;
int tris = verts-2;
if( CNFGVertPlace >= CNFG_BATCH-tris*3 ) CNFGFlushRender();
uint32_t color = CNFGLastColor;
short * ptrsrc = (short*)points;
for( i = 0; i < tris; i++ )
{
float * fv = &CNFGVertDataV[CNFGVertPlace*3];
fv[0] = ptrsrc[0];
fv[1] = ptrsrc[1];
fv[3] = ptrsrc[i*2+2];
fv[4] = ptrsrc[i*2+3];
fv[6] = ptrsrc[i*2+4];
fv[7] = ptrsrc[i*2+5];
uint32_t * col = &CNFGVertDataC[CNFGVertPlace];
col[0] = color;
col[1] = color;
col[2] = color;
CNFGVertPlace += 3;
}
}
uint32_t CNFGColor( uint32_t RGB )
{
return CNFGLastColor = RGB;
}
void CNFGSetLineWidth( short width )
{
wgl_last_width_over_2 = width/2.0f;// + 0.5;
}
#endif
#if !defined( __wasm__ ) && !defined( CNFGHTTP )
//In WASM, Javascript takes over this functionality.
//Shader compilation errors go to stderr.
#include <stdio.h>
#ifndef GL_VERTEX_SHADER
#define GL_FRAGMENT_SHADER 0x8B30
#define GL_VERTEX_SHADER 0x8B31
#define GL_COMPILE_STATUS 0x8B81
#define GL_INFO_LOG_LENGTH 0x8B84
#define GL_LINK_STATUS 0x8B82
#define GL_TEXTURE_2D 0x0DE1
#define GL_CLAMP_TO_EDGE 0x812F
#define LGLchar char
#else
#define LGLchar GLchar
#endif
#if defined(WINDOWS) || defined(WIN32) || defined(WIN64) || defined(_WIN32) || defined(_WIN64)
#define CNFGOGL_NEED_EXTENSION
#include <GL/gl.h>
#endif
#ifdef CNFGOGL_NEED_EXTENSION
// If we are going to be defining our own function pointer call
#if defined(WINDOWS) || defined(WIN32) || defined(WIN64) || defined(_WIN32) || defined(_WIN64)
// Make sure to use __stdcall on Windows
#define CHEWTYPEDEF( ret, name, rv, paramcall, ... ) \
typedef ret (__stdcall *CNFGTYPE##name)( __VA_ARGS__ ); \
ret (__stdcall *CNFG##name)( __VA_ARGS__ );
#else
#define CHEWTYPEDEF( ret, name, rv, paramcall, ... ) \
typedef ret (*CNFGTYPE##name)( __VA_ARGS__ ); \
ret (*CNFG##name)( __VA_ARGS__ );
#endif
#else
//If we are going to be defining the real call
#define CHEWTYPEDEF( ret, name, rv, paramcall, ... ) \
ret name (__VA_ARGS__);
#endif
#ifdef __cplusplus
extern "C" {
#endif
int (*MyFunc)( int program, const LGLchar *name );
CHEWTYPEDEF( GLint, glGetUniformLocation, return, (program,name), GLuint program, const LGLchar *name )
CHEWTYPEDEF( void, glEnableVertexAttribArray, , (index), GLuint index )
CHEWTYPEDEF( void, glUseProgram, , (program), GLuint program )
CHEWTYPEDEF( void, glGetProgramInfoLog, , (program,maxLength, length, infoLog), GLuint program, GLsizei maxLength, GLsizei *length, LGLchar *infoLog )
CHEWTYPEDEF( void, glGetProgramiv, , (program,pname,params), GLuint program, GLenum pname, GLint *params )
CHEWTYPEDEF( void, glBindAttribLocation, , (program,index,name), GLuint program, GLuint index, const LGLchar *name )
CHEWTYPEDEF( void, glGetShaderiv, , (shader,pname,params), GLuint shader, GLenum pname, GLint *params )
CHEWTYPEDEF( GLuint, glCreateShader, return, (e), GLenum e )
CHEWTYPEDEF( void, glVertexAttribPointer, , (index,size,type,normalized,stride,pointer), GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid * pointer )
CHEWTYPEDEF( void, glShaderSource, , (shader,count,string,length), GLuint shader, GLsizei count, const LGLchar *const*string, const GLint *length )
CHEWTYPEDEF( void, glAttachShader, , (program,shader), GLuint program, GLuint shader )
CHEWTYPEDEF( void, glCompileShader, ,(shader), GLuint shader )
CHEWTYPEDEF( void, glGetShaderInfoLog , , (shader,maxLength, length, infoLog), GLuint shader, GLsizei maxLength, GLsizei *length, LGLchar *infoLog )
CHEWTYPEDEF( GLuint, glCreateProgram, return, () , void )
CHEWTYPEDEF( void, glLinkProgram, , (program), GLuint program )
CHEWTYPEDEF( void, glDeleteShader, , (shader), GLuint shader )
CHEWTYPEDEF( void, glUniform4f, , (location,v0,v1,v2,v3), GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3 )
CHEWTYPEDEF( void, glUniform1i, , (location,i0), GLint location, GLint i0 )
CHEWTYPEDEF( void, glActiveTexture, , (texture), GLenum texture )
#ifndef CNFGOGL_NEED_EXTENSION
#define CNFGglGetUniformLocation glGetUniformLocation
#define CNFGglEnableVertexAttribArray glEnableVertexAttribArray
#define CNFGglUseProgram glUseProgram
#define CNFGglEnableVertexAttribArray glEnableVertexAttribArray
#define CNFGglUseProgram glUseProgram
#define CNFGglGetProgramInfoLog glGetProgramInfoLog
#define CNFGglGetProgramiv glGetProgramiv
#define CNFGglShaderSource glShaderSource
#define CNFGglCreateShader glCreateShader
#define CNFGglAttachShader glAttachShader
#define CNFGglGetShaderiv glGetShaderiv
#define CNFGglCompileShader glCompileShader
#define CNFGglGetShaderInfoLog glGetShaderInfoLog
#define CNFGglCreateProgram glCreateProgram
#define CNFGglLinkProgram glLinkProgram
#define CNFGglDeleteShader glDeleteShader
#define CNFGglUniform4f glUniform4f
#define CNFGglBindAttribLocation glBindAttribLocation
#define CNFGglVertexAttribPointer glVertexAttribPointer
#define CNFGglUniform1i glUniform1i
#define CNFGglActiveTexture glActiveTexture
#endif
#ifdef __cplusplus
};
#endif
#ifdef CNFGOGL_NEED_EXTENSION
#if defined( WIN32 ) || defined( WINDOWS ) || defined( WIN64 )
//From https://www.khronos.org/opengl/wiki/Load_OpenGL_Functions
void * CNFGGetProcAddress(const char *name)
{
void *p = (void *)wglGetProcAddress(name);
if(p == 0 ||
(p == (void*)0x1) || (p == (void*)0x2) || (p == (void*)0x3) ||
(p == (void*)-1) )
{
static HMODULE module;
if( !module ) module = LoadLibraryA("opengl32.dll");
p = (void *)GetProcAddress(module, name);
}
// We were unable to load the required openGL function
if (!p) {
fprintf(stderr,"[rawdraw][warn]: Unable to load openGL extension \"%s\"\n", name);
}
return p;
}
#else
#include <dlfcn.h>
void * CNFGGetProcAddress(const char *name)
{
//Tricky use RTLD_NEXT first so we don't accidentally link against ourselves.
void * v1 = dlsym( (void*)((intptr_t)-1) /*RTLD_NEXT = -1*/ /*RTLD_DEFAULT = 0*/, name );
//printf( "%s = %p\n", name, v1 );
if( !v1 ) v1 = dlsym( 0, name );
return v1;
}
#endif
// Try and load openGL extension functions required for rawdraw
static void CNFGLoadExtensionsInternal()
{
CNFGglGetUniformLocation = (CNFGTYPEglGetUniformLocation) CNFGGetProcAddress( "glGetUniformLocation" );
CNFGglEnableVertexAttribArray = (CNFGTYPEglEnableVertexAttribArray)CNFGGetProcAddress( "glEnableVertexAttribArray" );
CNFGglUseProgram = (CNFGTYPEglUseProgram)CNFGGetProcAddress( "glUseProgram" );
CNFGglGetProgramInfoLog = (CNFGTYPEglGetProgramInfoLog)CNFGGetProcAddress( "glGetProgramInfoLog" );
CNFGglBindAttribLocation = (CNFGTYPEglBindAttribLocation)CNFGGetProcAddress( "glBindAttribLocation" );
CNFGglGetProgramiv = (CNFGTYPEglGetProgramiv)CNFGGetProcAddress( "glGetProgramiv" );
CNFGglGetShaderiv = (CNFGTYPEglGetShaderiv)CNFGGetProcAddress( "glGetShaderiv" );
CNFGglVertexAttribPointer = (CNFGTYPEglVertexAttribPointer)CNFGGetProcAddress( "glVertexAttribPointer" );
CNFGglCreateShader = (CNFGTYPEglCreateShader)CNFGGetProcAddress( "glCreateShader" );
CNFGglShaderSource = (CNFGTYPEglShaderSource)CNFGGetProcAddress( "glShaderSource" );
CNFGglAttachShader = (CNFGTYPEglAttachShader)CNFGGetProcAddress( "glAttachShader" );
CNFGglCompileShader = (CNFGTYPEglCompileShader)CNFGGetProcAddress( "glCompileShader" );
CNFGglGetShaderInfoLog = (CNFGTYPEglGetShaderInfoLog)CNFGGetProcAddress( "glGetShaderInfoLog" );
CNFGglDeleteShader = (CNFGTYPEglDeleteShader)CNFGGetProcAddress( "glDeleteShader" );
CNFGglLinkProgram = (CNFGTYPEglLinkProgram)CNFGGetProcAddress( "glLinkProgram" );
CNFGglCreateProgram = (CNFGTYPEglCreateProgram)CNFGGetProcAddress( "glCreateProgram" );
CNFGglUniform4f = (CNFGTYPEglUniform4f)CNFGGetProcAddress( "glUniform4f" );
CNFGglUniform1i = (CNFGTYPEglUniform1i)CNFGGetProcAddress( "glUniform1i" );
CNFGglActiveTexture = (CNFGTYPEglActiveTexture)CNFGGetProcAddress("glActiveTexture");
// Check if any of these functions didn't get loaded
uint8_t not_all_functions_loaded =
!CNFGglGetUniformLocation || !CNFGglEnableVertexAttribArray || !CNFGglUseProgram ||
!CNFGglGetProgramInfoLog || !CNFGglBindAttribLocation || !CNFGglGetProgramiv ||
!CNFGglVertexAttribPointer || !CNFGglCreateShader || !CNFGglShaderSource ||
!CNFGglAttachShader || !CNFGglCompileShader || !CNFGglGetShaderInfoLog ||
!CNFGglDeleteShader || !CNFGglLinkProgram || !CNFGglCreateProgram ||
!CNFGglUniform4f || !CNFGglUniform1i || !CNFGglActiveTexture;
if (not_all_functions_loaded) {
fprintf(
stderr,
"[rawdraw][err]: Unable to load all openGL extensions required for rawdraw\n"
"\tPlease update your graphics drivers or unexpected crashes may occur.\n"
);
}
// Give a very stern warning if unable to create or compile shaders
if (!CNFGglCreateShader || !CNFGglCompileShader) {
fprintf(
stderr,
"[rawdraw][err]: Unable to create or compile shaders, this will cause a fatal error if "
"openGL is used.\n"
"\tUpdate your video graphics drivers or switch to software graphics.\n"
);
}
}
#else
static void CNFGLoadExtensionsInternal() { }
#endif
GLuint gRDShaderProg = -1;
GLuint gRDBlitProg = -1;
GLuint gRDShaderProgUX = -1;
GLuint gRDBlitProgUX = -1;
GLuint gRDBlitProgUT = -1;
GLuint gRDBlitProgTex = -1;
GLuint gRDLastResizeW;
GLuint gRDLastResizeH;
GLuint CNFGGLInternalLoadShader( const char * vertex_shader, const char * fragment_shader )
{
GLuint fragment_shader_object = 0;
GLuint vertex_shader_object = 0;
GLuint program = 0;
int ret;
vertex_shader_object = CNFGglCreateShader(GL_VERTEX_SHADER);
if (!vertex_shader_object) {
fprintf( stderr, "Error: glCreateShader(GL_VERTEX_SHADER) "
"failed: 0x%08X\n", glGetError());
goto fail;
}
CNFGglShaderSource(vertex_shader_object, 1, &vertex_shader, NULL);
CNFGglCompileShader(vertex_shader_object);
CNFGglGetShaderiv(vertex_shader_object, GL_COMPILE_STATUS, &ret);
if (!ret) {
fprintf( stderr,"Error: vertex shader compilation failed!\n");
CNFGglGetShaderiv(vertex_shader_object, GL_INFO_LOG_LENGTH, &ret);
if (ret > 1) {
//TODO: Refactor to remove malloc reliance.
#ifndef __clang__
char * log = (char*)alloca(ret);
CNFGglGetShaderInfoLog(vertex_shader_object, ret, NULL, log);
fprintf( stderr, "%s", log);
#endif
}
goto fail;
}
fragment_shader_object = CNFGglCreateShader(GL_FRAGMENT_SHADER);
if (!fragment_shader_object) {
fprintf( stderr, "Error: glCreateShader(GL_FRAGMENT_SHADER) "
"failed: 0x%08X\n", glGetError());
goto fail;
}
CNFGglShaderSource(fragment_shader_object, 1, &fragment_shader, NULL);
CNFGglCompileShader(fragment_shader_object);
CNFGglGetShaderiv(fragment_shader_object, GL_COMPILE_STATUS, &ret);
if (!ret) {
fprintf( stderr, "Error: fragment shader compilation failed!\n");
CNFGglGetShaderiv(fragment_shader_object, GL_INFO_LOG_LENGTH, &ret);
if (ret > 1) {
//TODO: Refactor to remove malloc reliance.
#ifndef __clang__
char * log = (char*)malloc(ret);
CNFGglGetShaderInfoLog(fragment_shader_object, ret, NULL, log);
fprintf( stderr, "%s", log);
free( log );
#endif
}
goto fail;
}
program = CNFGglCreateProgram();
if (!program) {
fprintf( stderr, "Error: failed to create program!\n");
goto fail;
}
CNFGglAttachShader(program, vertex_shader_object);
CNFGglAttachShader(program, fragment_shader_object);
CNFGglBindAttribLocation(program, 0, "a0");
CNFGglBindAttribLocation(program, 1, "a1");
CNFGglLinkProgram(program);
CNFGglGetProgramiv(program, GL_LINK_STATUS, &ret);
if (!ret) {
fprintf( stderr, "Error: program linking failed!\n");
CNFGglGetProgramiv(program, GL_INFO_LOG_LENGTH, &ret);
if (ret > 1) {
//TODO: Refactor to remove malloc reliance.
#ifndef __clang__
char *log = (char*)alloca(ret);
CNFGglGetProgramInfoLog(program, ret, NULL, log);
fprintf( stderr, "%s", log);
#endif
}
goto fail;
}
return program;
fail:
if( !vertex_shader_object ) CNFGglDeleteShader( vertex_shader_object );
if( !fragment_shader_object ) CNFGglDeleteShader( fragment_shader_object );
if( !program ) CNFGglDeleteShader( program );
return -1;
}
#if defined( CNFGEWGL ) && !defined( CNFG_NO_PRECISION )
#define PRECISIONA "lowp"
#define PRECISIONB "mediump"
#else
#define PRECISIONA
#define PRECISIONB
#endif
void CNFGSetupBatchInternal()
{
short w, h;
CNFGLoadExtensionsInternal();
CNFGGetDimensions( &w, &h );
gRDShaderProg = CNFGGLInternalLoadShader(
"uniform vec4 xfrm;"
"attribute vec3 a0;"
"attribute vec4 a1;"
"varying " PRECISIONA " vec4 vc;"
"void main() { gl_Position = vec4( a0.xy*xfrm.xy+xfrm.zw, a0.z, 0.5 ); vc = a1; }",
"varying " PRECISIONA " vec4 vc;"
"void main() { gl_FragColor = vec4(vc.abgr); }"
);
CNFGglUseProgram( gRDShaderProg );
gRDShaderProgUX = CNFGglGetUniformLocation ( gRDShaderProg , "xfrm" );
gRDBlitProg = CNFGGLInternalLoadShader(
"uniform vec4 xfrm;"
"attribute vec3 a0;"
"attribute vec4 a1;"
"varying " PRECISIONB " vec2 tc;"
"void main() { gl_Position = vec4( a0.xy*xfrm.xy+xfrm.zw, a0.z, 0.5 ); tc = a1.xy; }",
"varying " PRECISIONB " vec2 tc;"
"uniform sampler2D tex;"
"void main() { gl_FragColor = texture2D(tex,tc)."
#if !defined( CNFGRASTERIZER )
"wzyx"
#else
"wxyz"
#endif
";}" );
CNFGglUseProgram( gRDBlitProg );
gRDBlitProgUX = CNFGglGetUniformLocation ( gRDBlitProg , "xfrm" );
gRDBlitProgUT = CNFGglGetUniformLocation ( gRDBlitProg , "tex" );
glGenTextures( 1, &gRDBlitProgTex );
CNFGglEnableVertexAttribArray(0);
CNFGglEnableVertexAttribArray(1);
glDisable(GL_DEPTH_TEST);
glDepthMask( GL_FALSE );
glEnable( GL_BLEND );
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
CNFGVertPlace = 0;
}
#ifndef CNFGRASTERIZER
void CNFGInternalResize(short x, short y)
#else
void CNFGInternalResizeOGLBACKEND(short x, short y)
#endif
{
glViewport( 0, 0, x, y );
gRDLastResizeW = x;
gRDLastResizeH = y;
if (gRDShaderProg == 0xFFFFFFFF) { return; } // Prevent trying to set uniform if the shader isn't ready yet.
CNFGglUseProgram( gRDShaderProg );
CNFGglUniform4f( gRDShaderProgUX, 1.f/x, -1.f/y, -0.5f, 0.5f);
}
void CNFGEmitBackendTriangles( const float * vertices, const uint32_t * colors, int num_vertices )
{
CNFGglUseProgram( gRDShaderProg );
CNFGglUniform4f( gRDShaderProgUX, 1.f/gRDLastResizeW, -1.f/gRDLastResizeH, -0.5f, 0.5f);
CNFGglVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertices);
CNFGglVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, colors);
glDrawArrays( GL_TRIANGLES, 0, num_vertices);
}
#ifdef CNFGOGL
// this is here, so people don't have to include opengl
void CNFGDeleteTex( unsigned int tex )
{
glDeleteTextures(1, &tex);
}
unsigned int CNFGTexImage( uint32_t *data, int w, int h )
{
GLuint tex;
glGenTextures(1, &tex);
glEnable( GL_TEXTURE_2D );
CNFGglActiveTexture( 0 );
glBindTexture( GL_TEXTURE_2D, tex );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA,
GL_UNSIGNED_BYTE, data );
return (unsigned int)tex;
}
void CNFGBlitTex( unsigned int tex, int x, int y, int w, int h )
{
if( w == 0 || h == 0 ) return;
CNFGFlushRender();
CNFGglUseProgram( gRDBlitProg );
CNFGglUniform4f( gRDBlitProgUX,
1.f/gRDLastResizeW, -1.f/gRDLastResizeH,
-0.5f+x/(float)gRDLastResizeW, 0.5f-y/(float)gRDLastResizeH );
CNFGglUniform1i( gRDBlitProgUT, 0 );
glBindTexture(GL_TEXTURE_2D, tex);
const float verts[] = {
0,0, (float)w,0, (float)w,(float)h,
0,0, (float)w,(float)h, 0,(float)h, };
static const uint8_t tex_verts[] = {
0,0, 255,0, 255,255,
0,0, 255,255, 0,255 };
CNFGglVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, verts);
CNFGglVertexAttribPointer(1, 2, GL_UNSIGNED_BYTE, GL_TRUE, 0, tex_verts);
glDrawArrays( GL_TRIANGLES, 0, 6);
}
#endif
#ifdef CNFGRASTERIZER
void CNFGBlitImageInternal( uint32_t * data, int x, int y, int w, int h )
#else
void CNFGBlitImage( uint32_t * data, int x, int y, int w, int h )
#endif
{
glEnable( GL_TEXTURE_2D );
CNFGglActiveTexture( 0 );
glBindTexture( GL_TEXTURE_2D, gRDBlitProgTex );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA,
GL_UNSIGNED_BYTE, data );
CNFGBlitTex( gRDBlitProgTex, x, y, w, h );
}
void CNFGUpdateScreenWithBitmap( uint32_t * data, int w, int h )
{
#ifdef CNFGRASTERIZER
CNFGBlitImageInternal( data, 0, 0, w, h );
void CNFGSwapBuffersInternal();
CNFGSwapBuffersInternal();
#else
CNFGBlitImage( data, 0, 0, w, h );
#endif
}
#ifndef CNFGRASTERIZER
void CNFGFlushRender()
{
if( !CNFGVertPlace ) return;
CNFGEmitBackendTriangles( CNFGVertDataV, CNFGVertDataC, CNFGVertPlace );
CNFGVertPlace = 0;
}
void CNFGClearFrame()
{
glClearColor( ((CNFGBGColor&0xff000000)>>24)/255.0f,
((CNFGBGColor&0xff0000)>>16)/255.0f,
(CNFGBGColor&0xff00)/65280.0f,
(CNFGBGColor&0xff)/255.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
}
#endif
#endif //__wasm__
#else
void CNFGFlushRender() { }
#endif
#endif
#endif // CNFGHTTPSERVERONLY
#endif //_CNFG_C

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,70 @@
//Copyright (c) 2017 <>< Charles Lohr - Under the MIT/x11 or NewBSD License you choose.
#include "DrawFunctions.h"
static int w, h;
void CNFGGetDimensions( short * x, short * y )
{
*x = w;
*y = h;
}
static void InternalLinkScreenAndGo( const char * WindowName )
{
}
void CNFGSetupFullscreen( const char * WindowName, int screen_no )
{
CNFGSetup( WindowName, 640, 480 );
}
void CNFGTearDown()
{
}
int CNFGSetup( const char * WindowName, int sw, int sh )
{
w = sw;
h = sh;
return 0;
}
void CNFGHandleInput()
{
}
void CNFGUpdateScreenWithBitmap( uint32_t * data, int w, int h )
{
}
uint32_t CNFGColor( uint32_t RGB )
{
}
void CNFGClearFrame()
{
}
void CNFGSwapBuffers()
{
}
void CNFGTackSegment( short x1, short y1, short x2, short y2 )
{
}
void CNFGTackPixel( short x1, short y1 )
{
}
void CNFGTackRectangle( short x1, short y1, short x2, short y2 )
{
}
void CNFGTackPoly( RDPoint * points, int verts )
{
}

View file

@ -0,0 +1,189 @@
//Copyright (c) 2011, 2017, 2018 <>< Charles Lohr - Under the MIT/x11 or NewBSD License you choose.
//portions from
//https://github.com/NVIDIA-developer-blog/code-samples/blob/master/posts/egl_OpenGl_without_Xserver/tinyegl.cc
//NOTE: This is a truly incomplete driver - if no EGL surface is available, it does not support direct buffer rendering.
//Additionally no input is connected.
#include "CNFG.h"
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>
#include <X11/Xatom.h>
#include <X11/keysym.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef USE_EGL_GET_DISPLAY
#include <EGL/egl.h>
#else
#define EGL_EGLEXT_PROTOTYPES
#include <EGL/egl.h>
#include <EGL/eglext.h>
#endif
#ifdef USE_EGL_SURFACE
#include <GL/gl.h>
#else
#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <GL/glext.h>
#endif
static const EGLint configAttribs[] = {
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
EGL_RED_SIZE, 1,
EGL_GREEN_SIZE, 1,
EGL_BLUE_SIZE, 1,
EGL_DEPTH_SIZE, 1,
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
EGL_NONE
};
static int pbufferWidth = 0;
static int pbufferHeight = 0;
static EGLint pbufferAttribs[] = {
EGL_WIDTH, 0,
EGL_HEIGHT, 0,
EGL_NONE,
};
#ifdef CNFGOGL
#include <GL/glx.h>
#include <GL/glxext.h>
GLXContext CNFGCtx;
void * CNFGGetExtension( const char * extname ) { return glXGetProcAddressARB((const GLubyte *) extname); }
#endif
EGLDisplay eglDpy = 0;
EGLContext eglCtx = 0;
EGLSurface eglSurf = 0;
//These aren't actually used. Just here to allow linking.
XWindowAttributes CNFGWinAtt;
XClassHint *CNFGClassHint;
Display *CNFGDisplay;
Window CNFGWindow;
int CNFGWindowInvisible;
Pixmap CNFGPixmap;
GC CNFGGC;
GC CNFGWindowGC;
Visual * CNFGVisual;
int FullScreen = 0;
void CNFGGetDimensions( short * x, short * y )
{
*x = pbufferWidth;
*y = pbufferHeight;
}
void CNFGChangeWindowTitle( const char * WindowName )
{
}
void CNFGSetupFullscreen( const char * WindowName, int screen_no )
{
printf( "Full screen setup stubbed.\n" );
CNFGSetup( WindowName, 640, 480 );
}
void CNFGTearDown()
{
if( eglDpy )
{
eglTerminate( eglDpy );
}
//Unimplemented.
}
int CNFGSetup( const char * WindowName, int w, int h )
{
// glXMakeCurrent( CNFGDisplay, CNFGWindow, CNFGCtx );
eglDpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
atexit( CNFGTearDown );
printf( "EGL Display: %p\n", eglDpy );
if( eglDpy == 0 )
{
printf( "Trying alternate method.\n" );
// obtain display by specifying an appropriate device. This is the preferred method today.
// load the function pointers for the device,platform extensions
PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT =
(PFNEGLQUERYDEVICESEXTPROC) eglGetProcAddress("eglQueryDevicesEXT");
if(!eglQueryDevicesEXT) {
printf("ERROR: extension eglQueryDevicesEXT not available");
return(-1);
}
PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT =
(PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT");
if(!eglGetPlatformDisplayEXT) {
printf("ERROR: extension eglGetPlatformDisplayEXT not available");
return(-1);
}
static const int MAX_DEVICES = 16;
EGLDeviceEXT devices[MAX_DEVICES];
EGLint numDevices;
eglQueryDevicesEXT(MAX_DEVICES, devices, &numDevices);
eglDpy = eglGetPlatformDisplayEXT(EGL_PLATFORM_DEVICE_EXT, devices[0], 0);
printf( "EGL Backup Display: %p\n", eglDpy );
}
pbufferAttribs[1] = pbufferWidth = w;
pbufferAttribs[3] = pbufferHeight = h;
EGLint major, minor;
eglInitialize(eglDpy, &major, &minor);
EGLint numConfigs;
EGLConfig eglCfg;
eglChooseConfig(eglDpy, configAttribs, &eglCfg, 1, &numConfigs);
printf( "EGL Major Minor: %d %d\n", major, minor );
eglBindAPI(EGL_OPENGL_API);
eglCtx = eglCreateContext(eglDpy, eglCfg, EGL_NO_CONTEXT, NULL);
int err = eglGetError(); if(err != EGL_SUCCESS) { printf("1. Error %d\n", err); }
printf( "EGL Got context: %p\n", eglCtx );
if( w > 0 && h > 0 )
{
eglSurf = eglCreatePbufferSurface(eglDpy, eglCfg, pbufferAttribs);
eglMakeCurrent(eglDpy, eglSurf, eglSurf, eglCtx);
printf( "EGL Current, with surface %p\n", eglSurf );
//Actually have a surface. Need to allocate it.
}
else
{
eglMakeCurrent(eglDpy, EGL_NO_SURFACE, EGL_NO_SURFACE, eglCtx);
printf( "EGL Current, no surface.\n" );
}
return 0;
}
int CNFGHandleInput()
{
//Stubbed (No input)
return 1;
}
void CNFGUpdateScreenWithBitmap( unsigned long * data, int w, int h )
{
printf( "Stubbed update screen\n" );
}
void CNFGSetVSync( int vson )
{
//No-op
}
void CNFGSwapBuffers()
{
//No-op
}

View file

@ -0,0 +1,341 @@
//Don't call this file yourself. It is intended to be included in any drivers which want to support the rasterizer plugin.
#ifdef CNFGRASTERIZER
#include "CNFG.h"
//#include <stdlib.h>
#include <stdint.h>
uint32_t * CNFGBuffer = 0;
short CNFGBufferx;
short CNFGBuffery;
#ifdef CNFGOGL
void CNFGFlushRender()
{
}
#endif
void CNFGInternalResize( short x, short y )
{
CNFGBufferx = x;
CNFGBuffery = y;
if( CNFGBuffer ) free( CNFGBuffer );
CNFGBuffer = malloc( CNFGBufferx * CNFGBuffery * 4 );
#ifdef CNFGOGL
void CNFGInternalResizeOGLBACKEND( short w, short h );
CNFGInternalResizeOGLBACKEND( x, y );
#endif
}
#ifdef __wasm__
static uint32_t SWAPS( uint32_t r )
{
uint32_t ret = (r&0xFF)<<24;
r>>=8;
ret |= (r&0xff)<<16;
r>>=8;
ret |= (r&0xff)<<8;
r>>=8;
ret |= (r&0xff)<<0;
return ret;
}
#elif !defined(CNFGOGL)
#define SWAPS(x) (x>>8)
#else
static uint32_t SWAPS( uint32_t r )
{
uint32_t ret = (r&0xFF)<<16;
r>>=8;
ret |= (r&0xff)<<8;
r>>=8;
ret |= (r&0xff);
r>>=8;
ret |= (r&0xff)<<24;
return ret;
}
#endif
uint32_t CNFGColor( uint32_t RGB )
{
CNFGLastColor = SWAPS(RGB);
return CNFGLastColor;
}
void CNFGTackSegment( short x1, short y1, short x2, short y2 )
{
short tx, ty;
//float slope, lp;
float slope;
short dx = x2 - x1;
short dy = y2 - y1;
if( !CNFGBuffer ) return;
if( dx < 0 ) dx = -dx;
if( dy < 0 ) dy = -dy;
if( dx > dy )
{
short minx = (x1 < x2)?x1:x2;
short maxx = (x1 < x2)?x2:x1;
short miny = (x1 < x2)?y1:y2;
short maxy = (x1 < x2)?y2:y1;
float thisy = miny;
slope = (float)(maxy-miny) / (float)(maxx-minx);
for( tx = minx; tx <= maxx; tx++ )
{
ty = thisy;
if( tx < 0 || ty < 0 || ty >= CNFGBuffery ) continue;
if( tx >= CNFGBufferx ) break;
CNFGBuffer[ty * CNFGBufferx + tx] = CNFGLastColor;
thisy += slope;
}
}
else
{
short minx = (y1 < y2)?x1:x2;
short maxx = (y1 < y2)?x2:x1;
short miny = (y1 < y2)?y1:y2;
short maxy = (y1 < y2)?y2:y1;
float thisx = minx;
slope = (float)(maxx-minx) / (float)(maxy-miny);
for( ty = miny; ty <= maxy; ty++ )
{
tx = thisx;
if( ty < 0 || tx < 0 || tx >= CNFGBufferx ) continue;
if( ty >= CNFGBuffery ) break;
CNFGBuffer[ty * CNFGBufferx + tx] = CNFGLastColor;
thisx += slope;
}
}
}
void CNFGTackRectangle( short x1, short y1, short x2, short y2 )
{
short minx = (x1<x2)?x1:x2;
short miny = (y1<y2)?y1:y2;
short maxx = (x1>=x2)?x1:x2;
short maxy = (y1>=y2)?y1:y2;
short x, y;
if( minx < 0 ) minx = 0;
if( miny < 0 ) miny = 0;
if( maxx >= CNFGBufferx ) maxx = CNFGBufferx-1;
if( maxy >= CNFGBuffery ) maxy = CNFGBuffery-1;
for( y = miny; y <= maxy; y++ )
{
uint32_t * CNFGBufferstart = &CNFGBuffer[y * CNFGBufferx + minx];
for( x = minx; x <= maxx; x++ )
{
(*CNFGBufferstart++) = CNFGLastColor;
}
}
}
void CNFGTackPoly( RDPoint * points, int verts )
{
short minx = 10000, miny = 10000;
short maxx =-10000, maxy =-10000;
short i, x, y;
//Just in case...
if( verts > 32767 ) return;
for( i = 0; i < verts; i++ )
{
RDPoint * p = &points[i];
if( p->x < minx ) minx = p->x;
if( p->y < miny ) miny = p->y;
if( p->x > maxx ) maxx = p->x;
if( p->y > maxy ) maxy = p->y;
}
if( miny < 0 ) miny = 0;
if( maxy >= CNFGBuffery ) maxy = CNFGBuffery-1;
for( y = miny; y <= maxy; y++ )
{
short startfillx = maxx;
short endfillx = minx;
//Figure out what line segments intersect this line.
for( i = 0; i < verts; i++ )
{
short pl = i + 1;
if( pl == verts ) pl = 0;
RDPoint ptop;
RDPoint pbot;
ptop.x = points[i].x;
ptop.y = points[i].y;
pbot.x = points[pl].x;
pbot.y = points[pl].y;
//printf( "Poly: %d %d\n", pbot.y, ptop.y );
if( pbot.y < ptop.y )
{
RDPoint ptmp;
ptmp.x = pbot.x;
ptmp.y = pbot.y;
pbot.x = ptop.x;
pbot.y = ptop.y;
ptop.x = ptmp.x;
ptop.y = ptmp.y;
}
//Make sure this line segment is within our range.
//printf( "PT: %d %d %d\n", y, ptop.y, pbot.y );
if( ptop.y <= y && pbot.y >= y )
{
short diffy = pbot.y - ptop.y;
uint32_t placey = (uint32_t)(y - ptop.y)<<16; //Scale by 16 so we can do integer math.
short diffx = pbot.x - ptop.x;
short isectx;
if( diffy == 0 )
{
if( pbot.x < ptop.x )
{
if( startfillx > pbot.x ) startfillx = pbot.x;
if( endfillx < ptop.x ) endfillx = ptop.x;
}
else
{
if( startfillx > ptop.x ) startfillx = ptop.x;
if( endfillx < pbot.x ) endfillx = pbot.x;
}
}
else
{
//Inner part is scaled by 65536, outer part must be scaled back.
isectx = (( (placey / diffy) * diffx + 32768 )>>16) + ptop.x;
if( isectx < startfillx ) startfillx = isectx;
if( isectx > endfillx ) endfillx = isectx;
}
//printf( "R: %d %d %d\n", pbot.x, ptop.x, isectx );
}
}
//printf( "%d %d %d\n", y, startfillx, endfillx );
if( endfillx >= CNFGBufferx ) endfillx = CNFGBufferx - 1;
if( endfillx >= CNFGBufferx ) endfillx = CNFGBuffery - 1;
if( startfillx < 0 ) startfillx = 0;
if( startfillx < 0 ) startfillx = 0;
unsigned int * bufferstart = &CNFGBuffer[y * CNFGBufferx + startfillx];
for( x = startfillx; x <= endfillx; x++ )
{
(*bufferstart++) = CNFGLastColor;
}
}
//exit(1);
}
void CNFGClearFrame()
{
int i, m;
uint32_t col = 0;
short x, y;
CNFGGetDimensions( &x, &y );
if( x != CNFGBufferx || y != CNFGBuffery || !CNFGBuffer )
{
CNFGBufferx = x;
CNFGBuffery = y;
CNFGBuffer = malloc( x * y * 8 );
}
m = x * y;
col = CNFGColor( CNFGBGColor );
for( i = 0; i < m; i++ )
{
CNFGBuffer[i] = col;
}
}
void CNFGTackPixel( short x, short y )
{
if( x < 0 || y < 0 || x >= CNFGBufferx || y >= CNFGBuffery ) return;
CNFGBuffer[x+CNFGBufferx*y] = CNFGLastColor;
}
void CNFGBlitImage( uint32_t * data, int x, int y, int w, int h )
{
int ox = x;
int stride = w;
if( w <= 0 || h <= 0 || x >= CNFGBufferx || y >= CNFGBuffery ) return;
if( x < 0 ) { w += x; x = 0; }
if( y < 0 ) { h += y; y = 0; }
//Switch w,h to x2, y2
h += y;
w += x;
if( w >= CNFGBufferx ) { w = CNFGBufferx; }
if( h >= CNFGBuffery ) { h = CNFGBuffery; }
for( ; y < h-1; y++ )
{
x = ox;
uint32_t * indat = data;
uint32_t * outdat = CNFGBuffer + y * CNFGBufferx + x;
for( ; x < w-1; x++ )
{
uint32_t newm = *(indat++);
uint32_t oldm = *(outdat);
if( (newm & 0xff) == 0xff )
{
*(outdat++) = newm;
}
else
{
//Alpha blend.
int alfa = newm&0xff;
int onemalfa = 255-alfa;
#ifdef __wasm__
uint32_t newv = 255<<0; //Alpha, then RGB
newv |= ((((newm>>24)&0xff) * alfa + ((oldm>>24)&0xff) * onemalfa + 128)>>8)<<24;
newv |= ((((newm>>16)&0xff) * alfa + ((oldm>>16)&0xff) * onemalfa + 128)>>8)<<16;
newv |= ((((newm>>8)&0xff) * alfa + ((oldm>>8)&0xff) * onemalfa + 128)>>8)<<8;
#elif defined(WINDOWS) || defined(WIN32) || defined(WIN64) || defined(_WIN32) || defined(_WIN64)
uint32_t newv = 255UL<<24; //Alpha, then RGB
newv |= ((((newm>>24)&0xff) * alfa + ((oldm>>16)&0xff) * onemalfa + 128)>>8)<<16;
newv |= ((((newm>>16)&0xff) * alfa + ((oldm>>8)&0xff) * onemalfa + 128)>>8)<<8;
newv |= ((((newm>>8)&0xff) * alfa + ((oldm>>0)&0xff) * onemalfa + 128)>>8)<<0;
#elif defined( ANDROID ) || defined( __android__ )
uint32_t newv = 255<<16; //Alpha, then RGB
newv |= ((((newm>>24)&0xff) * alfa + ((oldm>>24)&0xff) * onemalfa + 128)>>8)<<24;
newv |= ((((newm>>16)&0xff) * alfa + ((oldm>>0)&0xff) * onemalfa + 128)>>8)<<0;
newv |= ((((newm>>8)&0xff) * alfa + ((oldm>>8)&0xff) * onemalfa + 128)>>8)<<8;
#elif defined( CNFGOGL ) //OGL, on X11
uint32_t newv = 255<<16; //Alpha, then RGB
newv |= ((((newm>>24)&0xff) * alfa + ((oldm>>24)&0xff) * onemalfa + 128)>>8)<<24;
newv |= ((((newm>>16)&0xff) * alfa + ((oldm>>0)&0xff) * onemalfa + 128)>>8)<<0;
newv |= ((((newm>>8)&0xff) * alfa + ((oldm>>8)&0xff) * onemalfa + 128)>>8)<<8;
#else //X11
uint32_t newv = 255UL<<24; //Alpha, then RGB
newv |= ((((newm>>24)&0xff) * alfa + ((oldm>>16)&0xff) * onemalfa + 128)>>8)<<16;
newv |= ((((newm>>16)&0xff) * alfa + ((oldm>>8)&0xff) * onemalfa + 128)>>8)<<8;
newv |= ((((newm>>8)&0xff) * alfa + ((oldm>>0)&0xff) * onemalfa + 128)>>8)<<0;
#endif
*(outdat++) = newv;
}
}
data += stride;
}
}
void CNFGSwapBuffers()
{
CNFGUpdateScreenWithBitmap( (uint32_t*)CNFGBuffer, CNFGBufferx, CNFGBuffery );
}
#endif

View file

@ -0,0 +1,84 @@
//Right now, designed for use with https://github.com/cnlohr/rawdrawwasm/
#include <CNFG.h>
#include <stdint.h>
extern void __attribute__((import_module("bynsyncify"))) CNFGSwapBuffersInternal();
void CNFGBlitImageInternal( uint32_t * data, int x, int y, int w, int h );
void print( double idebug );
void prints( const char* sdebug );
//Forward declarations that we get from either WASM or our javascript code.
void CNFGClearFrameInternal( uint32_t bgcolor );
//The WASM driver handles internal resizing automatically.
#ifndef CNFGRASTERIZER
void CNFGInternalResize( short x, short y )
{
}
void CNFGFlushRender()
{
if( !CNFGVertPlace ) return;
CNFGEmitBackendTriangles( CNFGVertDataV, CNFGVertDataC, CNFGVertPlace );
CNFGVertPlace = 0;
}
void CNFGClearFrame()
{
CNFGFlushRender();
CNFGClearFrameInternal( CNFGBGColor );
}
void CNFGSwapBuffers()
{
CNFGFlushRender();
CNFGSwapBuffersInternal( );
}
int CNFGHandleInput()
{
//Do nothing.
//Input is handled on swap frame.
return 1;
}
void CNFGBlitImage( uint32_t * data, int x, int y, int w, int h )
{
CNFGBlitImageInternal( data, x, y, w, h );
}
#else
//Rasterizer - if you want to do this, you will need to enable blitting in the javascript.
//XXX TODO: NEED MEMORY ALLOCATOR
extern unsigned char __heap_base;
unsigned int bump_pointer = (unsigned int)&__heap_base;
void* malloc(unsigned long size) {
unsigned int ptr = bump_pointer;
bump_pointer += size;
return (void *)ptr;
}
void free(void* ptr) { }
#include "CNFGRasterizer.c"
extern void CNFGUpdateScreenWithBitmapInternal( uint32_t * data, int w, int h );
void CNFGUpdateScreenWithBitmap( uint32_t * data, int w, int h )
{
CNFGBlitImageInternal( data, 0, 0, w, h );
CNFGSwapBuffersInternal();
}
void CNFGSetLineWidth( short width )
{
//Rasterizer does not support line width.
}
void CNFGHandleInput()
{
//Do nothing.
//Input is handled on swap frame.
}
#endif

View file

@ -0,0 +1,681 @@
//Copyright (c) 2011-2019 <>< Charles Lohr - Under the MIT/x11 or NewBSD License you choose.
//Portion from: http://en.wikibooks.org/wiki/Windows_Programming/Window_Creation
#ifndef _CNFGWINDRIVER_C
#define _CNFGWINDRIVER_C
#include "CNFG.h"
#include <windows.h>
#include <stdlib.h>
#include <ctype.h>
HBITMAP CNFGlsBitmap;
HWND CNFGlsHWND;
HDC CNFGlsWindowHDC;
HDC CNFGlsHDC;
HDC CNFGlsHDCBlit;
int ShouldClose = 0;
//Queue up lines and points for a faster render.
#ifndef CNFG_WINDOWS_DISABLE_BATCH
#define BATCH_ELEMENTS
#endif
#define COLORSWAPS( RGB ) \
((((RGB )& 0xFF000000)>>24) | ( ((RGB )& 0xFF0000 ) >> 8 ) | ( ((RGB )& 0xFF00 )<<8 ))
void CNFGChangeWindowTitle( const char * windowtitle )
{
SetWindowTextA( CNFGlsHWND, windowtitle );
}
#ifdef CNFGRASTERIZER
#include "CNFGRasterizer.c"
void InternalHandleResize()
{
if( CNFGlsBitmap ) DeleteObject( CNFGlsBitmap );
CNFGInternalResize( CNFGBufferx, CNFGBuffery );
CNFGlsBitmap = CreateBitmap( CNFGBufferx, CNFGBuffery, 1, 32, CNFGBuffer );
SelectObject( CNFGlsHDC, CNFGlsBitmap );
CNFGInternalResize( CNFGBufferx, CNFGBuffery);
}
#else
static short CNFGBufferx, CNFGBuffery;
static void InternalHandleResize();
#endif
#ifdef CNFGOGL
#include <GL/gl.h>
static HGLRC hRC=NULL;
static void InternalHandleResize() { }
void CNFGSwapBuffers()
{
#ifdef CNFG_BATCH
#ifndef CNFGCONTEXTONLY
CNFGFlushRender();
#endif
#endif
SwapBuffers(CNFGlsWindowHDC);
}
#endif
CNFGCursorShape CNFGCurShape = CNFG_CURSOR_ARROW;
void CNFGSetMousePosition( int x, int y ) {
RECT window;
int bordersize = GetSystemMetrics( SM_CXSIZEFRAME );
GetWindowRect( CNFGlsHWND, &window );
x += window.left + bordersize;
y += window.top + GetSystemMetrics( SM_CYCAPTION ) + bordersize;
SetCursorPos( x, y );
}
void CNFGConfineMouse( int confined ) {
if ( !confined ) {
ClipCursor( NULL );
return;
}
int bordersize = GetSystemMetrics( SM_CXSIZEFRAME );
RECT window;
GetWindowRect( CNFGlsHWND, &window );
window.left += bordersize;
window.top += GetSystemMetrics( SM_CYCAPTION ) + bordersize;
window.right -= bordersize;
window.bottom -= bordersize;
ClipCursor( &window );
}
void CNFGSetCursor( CNFGCursorShape shape ) {
if (shape == CNFGCurShape ) return;
// If the current shape's visibility is the opposite of the new shape, toggle the cursor visibility
// According to MSDN, ShowCursor increments and decrements an internal counter, which is used to determine visibility
// The internal counter seemingly has no limit
if ( shape == CNFG_CURSOR_HIDDEN && CNFGCurShape != CNFG_CURSOR_HIDDEN ) ShowCursor( FALSE );
else if ( shape != CNFG_CURSOR_HIDDEN && CNFGCurShape == CNFG_CURSOR_HIDDEN ) ShowCursor( TRUE );
CNFGCurShape = shape;
}
void CNFGGetDimensions( short * x, short * y )
{
static short lastx, lasty;
RECT window;
GetClientRect( CNFGlsHWND, &window );
CNFGBufferx = (short)( window.right - window.left);
CNFGBuffery = (short)( window.bottom - window.top);
if( CNFGBufferx != lastx || CNFGBuffery != lasty )
{
lastx = CNFGBufferx;
lasty = CNFGBuffery;
#ifndef CNFGCONTEXTONLY
CNFGInternalResize( lastx, lasty );
#endif
InternalHandleResize();
}
*x = CNFGBufferx;
*y = CNFGBuffery;
}
#ifndef CNFGOGL
void CNFGUpdateScreenWithBitmap( uint32_t * data, int w, int h )
{
RECT r;
SelectObject( CNFGlsHDC, CNFGlsBitmap );
SetBitmapBits(CNFGlsBitmap,w*h*4,data);
BitBlt(CNFGlsWindowHDC, 0, 0, w, h, CNFGlsHDC, 0, 0, SRCCOPY);
UpdateWindow( CNFGlsHWND );
short thisw, thish;
//Check to see if the window is closed.
if( !IsWindow( CNFGlsHWND ) )
{
exit( 0 );
}
GetClientRect( CNFGlsHWND, &r );
thisw = (short)(r.right - r.left);
thish = (short)(r.bottom - r.top);
if( thisw != CNFGBufferx || thish != CNFGBuffery )
{
CNFGBufferx = thisw;
CNFGBuffery = thish;
InternalHandleResize();
}
}
#endif
void CNFGTearDown()
{
PostQuitMessage(0);
ShouldClose = 1;
}
//This was from the article
LRESULT CALLBACK MyWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
#ifndef CNFGOGL
case WM_SYSCOMMAND: //Not sure why, if deactivated, the dc gets unassociated?
if( wParam == SC_RESTORE || wParam == SC_MAXIMIZE || wParam == SC_SCREENSAVE )
{
SelectObject( CNFGlsHDC, CNFGlsBitmap );
SelectObject( CNFGlsWindowHDC, CNFGlsBitmap );
}
break;
#endif
case WM_KILLFOCUS:
case WM_SETFOCUS:
CNFGLastScancode = 0;
HandleKey( CNFG_KEY_FOCUS, msg == WM_SETFOCUS );
return 0;
case WM_CLOSE:
if( HandleDestroy() )
return 0;
break;
case WM_DESTROY:
CNFGTearDown();
return 0;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
int CNFGSetupWinInternal( const char * name_of_window, int width, int height, int isFullscreen );
void CNFGSetupFullscreen( const char * WindowName, int screen_number )
{
// Get primary monitor dimensions, but default to 1920x1080
int monitorW = GetSystemMetrics(SM_CXSCREEN);
if(0 == monitorW)
{
monitorW = 1920;
}
int monitorH = GetSystemMetrics(SM_CYSCREEN);
if(0 == monitorH)
{
monitorH = 1080;
}
CNFGSetupWinInternal(WindowName, monitorW, monitorH, 1);
}
int CNFGSetup( const char * name_of_window, int width, int height )
{
return CNFGSetupWinInternal(name_of_window, width, height, 0);
}
//This was from the article, too... well, mostly.
int CNFGSetupWinInternal( const char * name_of_window, int width, int height, int isFullscreen )
{
static LPCSTR szClassName = "MyClass";
RECT client, window;
WNDCLASSA wnd;
int w, h, wd, hd;
int show_window = 1;
HINSTANCE hInstance = GetModuleHandle(NULL);
if( width < 0 )
{
show_window = 0;
width = -width;
}
if( height < 0 )
{
show_window = 0;
height = -height;
}
CNFGBufferx = (short)width;
CNFGBuffery = (short)height;
wnd.style = CS_HREDRAW | CS_VREDRAW; //we will explain this later
wnd.lpfnWndProc = MyWndProc;
wnd.cbClsExtra = 0;
wnd.cbWndExtra = 0;
wnd.hInstance = hInstance;
wnd.hIcon = LoadIcon(NULL, IDI_APPLICATION); //default icon
wnd.hCursor = LoadCursor(NULL, IDC_ARROW); //default arrow mouse cursor
wnd.hbrBackground = (HBRUSH)(COLOR_BACKGROUND);
wnd.lpszMenuName = NULL; //no menu
wnd.lpszClassName = szClassName;
if(!RegisterClassA(&wnd)) //register the WNDCLASS
{
MessageBoxA(NULL, "This Program Requires Windows NT", "Error", MB_OK);
}
#ifdef UNICODE
// CreateWindowA **requires** unicode window name even in non-unicode mode.
int wlen = strlen( name_of_window );
char * unicodeao = (char*)alloca( wlen * 2 + 2 );
int i;
for( i = 0; i <= wlen; i++ )
{
unicodeao[i * 2 + 1] = 0;
unicodeao[i * 2 + 0] = name_of_window[i];
}
name_of_window = unicodeao;
#endif
CNFGlsHWND = CreateWindowA(szClassName,
name_of_window, //name_of_window, but must always be
isFullscreen ? (WS_MAXIMIZE | WS_POPUP) : (WS_OVERLAPPEDWINDOW), //basic window style
CW_USEDEFAULT,
CW_USEDEFAULT, //set starting point to default value
CNFGBufferx,
CNFGBuffery, //set all the dimensions to default value
NULL, //no parent window
NULL, //no menu
hInstance,
NULL); //no parameters to pass
CNFGlsWindowHDC = GetDC( CNFGlsHWND );
#ifdef CNFGOGL
//From NeHe
static PIXELFORMATDESCRIPTOR pfd =
{
sizeof(PIXELFORMATDESCRIPTOR),
1,
PFD_DRAW_TO_WINDOW |
PFD_SUPPORT_OPENGL |
PFD_DOUBLEBUFFER,
PFD_TYPE_RGBA,
24,
8, 0, 8, 8, 8, 16,
8,
24,
32,
8, 8, 8, 8,
16,
0,
0,
PFD_MAIN_PLANE,
0,
0, 0, 0
};
GLuint PixelFormat = ChoosePixelFormat( CNFGlsWindowHDC, &pfd );
if( !SetPixelFormat( CNFGlsWindowHDC, PixelFormat, &pfd ) )
{
MessageBoxA( 0, "Could not create PFD for OpenGL Context\n", 0, 0 );
exit( -1 );
}
if (!(hRC=wglCreateContext(CNFGlsWindowHDC))) // Are We Able To Get A Rendering Context?
{
MessageBoxA( 0, "Could not create OpenGL Context\n", 0, 0 );
exit( -1 );
}
if(!wglMakeCurrent(CNFGlsWindowHDC,hRC)) // Try To Activate The Rendering Context
{
MessageBoxA( 0, "Could not current OpenGL Context\n", 0, 0 );
exit( -1 );
}
#endif
CNFGlsHDC = CreateCompatibleDC( CNFGlsWindowHDC );
CNFGlsHDCBlit = CreateCompatibleDC( CNFGlsWindowHDC );
CNFGlsBitmap = CreateCompatibleBitmap( CNFGlsWindowHDC, CNFGBufferx, CNFGBuffery );
SelectObject( CNFGlsHDC, CNFGlsBitmap );
//lsClearBrush = CreateSolidBrush( CNFGBGColor );
//lsHBR = CreateSolidBrush( 0xFFFFFF );
//lsHPEN = CreatePen( PS_SOLID, 0, 0xFFFFFF );
if( show_window )
ShowWindow(CNFGlsHWND, isFullscreen ? SW_SHOWMAXIMIZED : SW_SHOWNORMAL); //display the window on the screen
//Once set up... we have to change the window's borders so we get the client size right.
GetClientRect( CNFGlsHWND, &client );
GetWindowRect( CNFGlsHWND, &window );
w = ( window.right - window.left);
h = ( window.bottom - window.top);
wd = w - client.right;
hd = h - client.bottom;
MoveWindow( CNFGlsHWND, window.left, window.top, CNFGBufferx + wd, CNFGBuffery + hd, 1 );
InternalHandleResize();
#ifdef CNFG_BATCH
#ifndef CNFGCONTEXTONLY
CNFGSetupBatchInternal();
#endif
#endif
ShouldClose = 0;
return 0;
}
int CNFGHandleInput()
{
#ifdef CNFGOGL
if (ShouldClose)
exit(0);
#endif
MSG msg;
while( PeekMessage( &msg, NULL, 0, 0xFFFF, 1 ) )
{
TranslateMessage(&msg);
MSG charMSG;
switch( msg.message )
{
case WM_MOUSEMOVE:
HandleMotion( (msg.lParam & 0xFFFF), (msg.lParam>>16) & 0xFFFF, ( (msg.wParam & 0x01)?1:0) | ((msg.wParam & 0x02)?2:0) | ((msg.wParam & 0x10)?4:0) );
break;
case WM_LBUTTONDOWN: HandleButton( (msg.lParam & 0xFFFF), (msg.lParam>>16) & 0xFFFF, 1, 1 ); break;
case WM_RBUTTONDOWN: HandleButton( (msg.lParam & 0xFFFF), (msg.lParam>>16) & 0xFFFF, 2, 1 ); break;
case WM_MBUTTONDOWN: HandleButton( (msg.lParam & 0xFFFF), (msg.lParam>>16) & 0xFFFF, 3, 1 ); break;
case WM_LBUTTONUP: HandleButton( (msg.lParam & 0xFFFF), (msg.lParam>>16) & 0xFFFF, 1, 0 ); break;
case WM_RBUTTONUP: HandleButton( (msg.lParam & 0xFFFF), (msg.lParam>>16) & 0xFFFF, 2, 0 ); break;
case WM_MBUTTONUP: HandleButton( (msg.lParam & 0xFFFF), (msg.lParam>>16) & 0xFFFF, 3, 0 ); break;
case WM_KEYDOWN:
// Check if there is a WM_CHAR message in the queue. If there is one, put it into CNFGLastCharacter
// Otherwise, we don't want HandleKey to handle the wrong character, so set to 0
if (PeekMessage(&charMSG, NULL, WM_CHAR, WM_CHAR, PM_REMOVE)) CNFGLastCharacter = charMSG.wParam;
else CNFGLastCharacter = 0;
// fall through
case WM_KEYUP:
CNFGLastScancode = (msg.lParam >> 16) & 0xFF;
if (msg.lParam & 0x01000000) HandleKey( (int) msg.wParam + 0x7C , (msg.message==WM_KEYDOWN) );
else HandleKey( (int) msg.wParam, (msg.message==WM_KEYDOWN) );
// Don't confuse the program when CNFGLastCharacter is set on WM_KEYUP.
// Since we shouldn't be using CNFGLastCharacter outside HandleKey anyways, just clear it here.
CNFGLastCharacter = 0;
break;
case WM_MOUSEWHEEL:
{
POINT p = { 0 };
p.x = LOWORD( msg.lParam );
p.y = HIWORD( msg.lParam );
ScreenToClient(CNFGlsHWND, &p);
HandleButton(p.x, p.y, GET_WHEEL_DELTA_WPARAM(msg.wParam) > 0 ? 0x0E : 0x0F, 1);
} break;
default:
DispatchMessage(&msg);
break;
}
}
return !ShouldClose;
}
#ifndef CNFGOGL
#ifndef CNFGRASTERIZER
static HBITMAP lsBackBitmap;
static HBRUSH lsHBR;
static HPEN lsHPEN;
static HBRUSH lsClearBrush;
static void InternalHandleResize()
{
DeleteObject( lsBackBitmap );
lsBackBitmap = CreateCompatibleBitmap( CNFGlsHDC, CNFGBufferx, CNFGBuffery );
SelectObject( CNFGlsHDC, lsBackBitmap );
}
#ifdef BATCH_ELEMENTS
static int linelisthead;
static int pointlisthead;
static int polylisthead;
static int polylistindex;
static POINT linelist[4096*3];
static DWORD twoarray[4096];
static POINT pointlist[4096];
static POINT polylist[8192];
static INT polylistcutoffs[8192];
static int last_linex;
static int last_liney;
static int possible_lastline;
void FlushTacking()
{
int i;
if( twoarray[0] != 2 )
for( i = 0; i < 4096; i++ ) twoarray[i] = 2;
if( linelisthead )
{
PolyPolyline( CNFGlsHDC, linelist, twoarray, linelisthead );
linelisthead = 0;
}
if( polylistindex )
{
PolyPolygon( CNFGlsHDC, polylist, polylistcutoffs, polylistindex );
polylistindex = 0;
polylisthead = 0;
}
if( possible_lastline )
CNFGTackPixel( (short)last_linex, (short)last_liney );
possible_lastline = 0;
//XXX TODO: Consider locking the bitmap, and manually drawing the pixels.
if( pointlisthead )
{
for( i = 0; i < pointlisthead; i++ )
{
SetPixel( CNFGlsHDC, pointlist[i].x, pointlist[i].y, CNFGLastColor );
}
pointlisthead = 0;
}
}
#endif
uint32_t CNFGColor( uint32_t RGB )
{
RGB = COLORSWAPS( RGB );
if( CNFGLastColor == RGB ) return RGB;
#ifdef BATCH_ELEMENTS
FlushTacking();
#endif
CNFGLastColor = RGB;
DeleteObject( lsHBR );
lsHBR = CreateSolidBrush( RGB );
SelectObject( CNFGlsHDC, lsHBR );
DeleteObject( lsHPEN );
lsHPEN = CreatePen( PS_SOLID, 0, RGB );
SelectObject( CNFGlsHDC, lsHPEN );
return RGB;
}
void CNFGBlitImage( uint32_t * data, int x, int y, int w, int h )
{
static int pbw, pbh;
static HBITMAP pbb;
if( !pbb || pbw != w || pbh !=h )
{
if( pbb ) DeleteObject( pbb );
pbb = CreateBitmap( w, h, 1, 32, 0 );
pbh = h;
pbw = w;
}
SetBitmapBits(pbb,w*h*4,data);
SelectObject( CNFGlsHDCBlit, pbb );
BitBlt(CNFGlsHDC, x, y, w, h, CNFGlsHDCBlit, 0, 0, SRCCOPY);
}
void CNFGTackSegment( short x1, short y1, short x2, short y2 )
{
#ifdef BATCH_ELEMENTS
if( ( x1 != last_linex || y1 != last_liney ) && possible_lastline )
{
CNFGTackPixel( (short)last_linex, (short)last_liney );
}
if( x1 == x2 && y1 == y2 )
{
CNFGTackPixel( x1, y1 );
possible_lastline = 0;
return;
}
last_linex = x2;
last_liney = y2;
possible_lastline = 1;
if( x1 != x2 || y1 != y2 )
{
linelist[linelisthead*2+0].x = x1;
linelist[linelisthead*2+0].y = y1;
linelist[linelisthead*2+1].x = x2;
linelist[linelisthead*2+1].y = y2;
linelisthead++;
if( linelisthead >= 2048 ) FlushTacking();
}
#else
POINT pt[2] = { {x1, y1}, {x2, y2} };
Polyline( CNFGlsHDC, pt, 2 );
SetPixel( CNFGlsHDC, x1, y1, CNFGLastColor );
SetPixel( CNFGlsHDC, x2, y2, CNFGLastColor );
#endif
}
void CNFGTackRectangle( short x1, short y1, short x2, short y2 )
{
#ifdef BATCH_ELEMENTS
FlushTacking();
#endif
RECT r;
if( x1 < x2 ) { r.left = x1; r.right = x2; }
else { r.left = x2; r.right = x1; }
if( y1 < y2 ) { r.top = y1; r.bottom = y2; }
else { r.top = y2; r.bottom = y1; }
FillRect( CNFGlsHDC, &r, lsHBR );
}
void CNFGClearFrame()
{
#ifdef BATCH_ELEMENTS
FlushTacking();
#endif
RECT r = { 0, 0, CNFGBufferx, CNFGBuffery };
DeleteObject( lsClearBrush );
lsClearBrush = CreateSolidBrush( COLORSWAPS(CNFGBGColor) );
HBRUSH prevBrush = (HBRUSH)SelectObject( CNFGlsHDC, lsClearBrush );
FillRect( CNFGlsHDC, &r, lsClearBrush);
SelectObject( CNFGlsHDC, prevBrush );
}
void CNFGTackPoly( RDPoint * points, int verts )
{
#ifdef BATCH_ELEMENTS
if( verts > 8192 )
{
FlushTacking();
//Fall-through
}
else
{
if( polylistindex >= 8191 || polylisthead + verts >= 8191 )
{
FlushTacking();
}
int i;
for( i = 0; i < verts; i++ )
{
polylist[polylisthead].x = points[i].x;
polylist[polylisthead].y = points[i].y;
polylisthead++;
}
polylistcutoffs[polylistindex++] = verts;
return;
}
#endif
{
int i;
POINT * t = (POINT*)alloca( sizeof( POINT ) * verts );
for( i = 0; i < verts; i++ )
{
t[i].x = points[i].x;
t[i].y = points[i].y;
}
Polygon( CNFGlsHDC, t, verts );
}
}
void CNFGTackPixel( short x1, short y1 )
{
#ifdef BATCH_ELEMENTS
pointlist[pointlisthead+0].x = x1;
pointlist[pointlisthead+0].y = y1;
pointlisthead++;
if( pointlisthead >=4096 ) FlushTacking();
#else
SetPixel( CNFGlsHDC, x1, y1, CNFGLastColor );
#endif
}
void CNFGSwapBuffers()
{
#ifdef BATCH_ELEMENTS
FlushTacking();
#endif
int thisw, thish;
RECT r;
BitBlt( CNFGlsWindowHDC, 0, 0, CNFGBufferx, CNFGBuffery, CNFGlsHDC, 0, 0, SRCCOPY );
UpdateWindow( CNFGlsHWND );
//Check to see if the window is closed.
if( !IsWindow( CNFGlsHWND ) )
{
exit( 0 );
}
GetClientRect( CNFGlsHWND, &r );
thisw = r.right - r.left;
thish = r.bottom - r.top;
if( thisw != CNFGBufferx || thish != CNFGBuffery )
{
CNFGBufferx = (short)thisw;
CNFGBuffery = (short)thish;
InternalHandleResize();
}
}
void CNFGInternalResize( short bfx, short bfy ) { }
#endif
#endif
#endif // _CNFGWINDRIVER_C

View file

@ -0,0 +1,684 @@
//Copyright (c) 2011, 2017, 2018 <>< Charles Lohr - Under the MIT/x11 or NewBSD License you choose.
//portions from
//http://www.xmission.com/~georgeps/documentation/tutorials/Xlib_Beginner.html
//#define HAS_XINERAMA
//#define CNFG_HAS_XSHAPE
//#define FULL_SCREEN_STEAL_FOCUS
#ifndef _CNFGXDRIVER_C
#define _CNFGXDRIVER_C
#include "CNFG.h"
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>
#include <X11/Xatom.h>
#include <X11/cursorfont.h>
#include <X11/keysym.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef HAS_XINERAMA
#include <X11/extensions/shape.h>
#include <X11/extensions/Xinerama.h>
#endif
#ifdef CNFG_HAS_XSHAPE
#include <X11/extensions/shape.h>
static XGCValues xsval;
static Pixmap xspixmap;
static GC xsgc;
static int taint_shape;
static int prepare_xshape;
static int was_transp;
#endif
#ifdef CNFG_BATCH
void CNFGSetupBatchInternal();
#endif
XWindowAttributes CNFGWinAtt;
XClassHint *CNFGClassHint;
char * wm_res_name = 0;
char * wm_res_class = 0;
Display *CNFGDisplay;
Atom CFNGWMDeleteWindow;
Window CNFGWindow;
int CNFGWindowInvisible;
Pixmap CNFGPixmap;
GC CNFGGC;
GC CNFGWindowGC;
int CNFGDepth;
int CNFGScreen;
Visual * CNFGVisual;
VisualID CNFGVisualID;
#ifdef CNFGOGL
#include <GL/glx.h>
#include <GL/glxext.h>
GLXContext CNFGCtx;
void * CNFGGetExtension( const char * extname ) { return (void*)glXGetProcAddressARB((const GLubyte *) extname); }
GLXFBConfig CNFGGLXFBConfig;
GLXFBConfig* CNFGGLXFBConfigs;
void CNFGGLXSetup( )
{
int attribs[] = {
GLX_RENDER_TYPE, GLX_RGBA_BIT,
GLX_DOUBLEBUFFER, True,
GLX_RED_SIZE, 1,
GLX_GREEN_SIZE, 1,
GLX_BLUE_SIZE, 1,
GLX_DEPTH_SIZE, 1,
None };
int elements = 0;
GLXFBConfig * cfgs = glXChooseFBConfig( CNFGDisplay, CNFGScreen, attribs, &elements );
if( elements == 0 )
{
fprintf( stderr, "Error: could not get valid GLXFBConfig visual.\n" );
exit( -1 );
}
CNFGGLXFBConfigs = cfgs;
CNFGGLXFBConfig = cfgs[0];
XVisualInfo * vis = glXGetVisualFromFBConfig( CNFGDisplay, CNFGGLXFBConfig );
CNFGVisual = vis->visual;
CNFGVisualID = vis->visualid;
CNFGDepth = vis->depth;
CNFGCtx = glXCreateContext( CNFGDisplay, vis, NULL, True );
XFree( vis );
}
#endif
int CNFGX11ForceNoDecoration;
XImage *xi;
XIM CNFGXIM = NULL;
XIC CNFGXIC = NULL;
int g_x_global_key_state;
int g_x_global_shift_key;
void CNFGSetWindowIconData( int w, int h, uint32_t * data )
{
static Atom net_wm_icon;
static Atom cardinal;
if( !net_wm_icon ) net_wm_icon = XInternAtom( CNFGDisplay, "_NET_WM_ICON", False );
if( !cardinal ) cardinal = XInternAtom( CNFGDisplay, "CARDINAL", False );
unsigned long outdata[w*h];
int i;
for( i = 0; i < w*h; i++ )
{
outdata[i+2] = data[i];
}
outdata[0] = w;
outdata[1] = h;
XChangeProperty(CNFGDisplay, CNFGWindow, net_wm_icon, cardinal,
32, PropModeReplace, (const unsigned char*)outdata, 2 + w*h);
}
#ifdef CNFG_HAS_XSHAPE
void CNFGPrepareForTransparency() { prepare_xshape = 1; }
void CNFGDrawToTransparencyMode( int transp )
{
static Pixmap BackupCNFGPixmap;
static GC BackupCNFGGC;
if( was_transp && ! transp )
{
CNFGGC = BackupCNFGGC;
CNFGPixmap = BackupCNFGPixmap;
}
if( !was_transp && transp )
{
BackupCNFGPixmap = CNFGPixmap;
BackupCNFGGC = CNFGGC;
taint_shape = 1;
CNFGGC = xsgc;
CNFGPixmap = xspixmap;
}
was_transp = transp;
}
void CNFGClearTransparencyLevel()
{
taint_shape = 1;
XSetForeground(CNFGDisplay, xsgc, 0);
XFillRectangle(CNFGDisplay, xspixmap, xsgc, 0, 0, CNFGWinAtt.width, CNFGWinAtt.height);
XSetForeground(CNFGDisplay, xsgc, 1);
}
#endif
void CNFGSetMousePosition( int x, int y ) {
XWarpPointer( CNFGDisplay, None, CNFGWindow, 0, 0, 0, 0, x, y );
}
void CNFGConfineMouse( int confined ) {
if (confined) {
XGrabPointer( CNFGDisplay, CNFGWindow, True, 0, GrabModeAsync, GrabModeAsync, CNFGWindow, None, CurrentTime );
} else {
XUngrabPointer( CNFGDisplay, CurrentTime );
}
}
Cursor CNFGCursors[CNFG_CURSOR_LAST] = { None };
void CNFGSetCursor( CNFGCursorShape shape ) {
if (shape == CNFG_CURSOR_ARROW) XUndefineCursor( CNFGDisplay, CNFGWindow );
else XDefineCursor( CNFGDisplay, CNFGWindow, CNFGCursors[shape] );
}
int FullScreen = 0;
void CNFGGetDimensions( short * x, short * y )
{
static int lastx;
static int lasty;
*x = CNFGWinAtt.width;
*y = CNFGWinAtt.height;
if( lastx != *x || lasty != *y )
{
lastx = *x;
lasty = *y;
#ifndef CNFGCONTEXTONLY
CNFGInternalResize( lastx, lasty );
#endif
}
}
void CNFGChangeWindowTitle( const char * WindowName )
{
XSetStandardProperties( CNFGDisplay, CNFGWindow, WindowName, 0, 0, 0, 0, 0 );
}
static void InternalLinkScreenAndGo( const char * WindowName )
{
XFlush(CNFGDisplay);
XGetWindowAttributes( CNFGDisplay, CNFGWindow, &CNFGWinAtt );
if( !wm_res_name ) wm_res_name = strdup( "rawdraw" );
if( !wm_res_class ) wm_res_class = strdup( "rawdraw" );
XGetClassHint( CNFGDisplay, CNFGWindow, CNFGClassHint );
if (!CNFGClassHint) {
CNFGClassHint = XAllocClassHint();
if (CNFGClassHint) {
CNFGClassHint->res_name = wm_res_name;
CNFGClassHint->res_class = wm_res_class;
XSetClassHint( CNFGDisplay, CNFGWindow, CNFGClassHint );
} else {
fprintf( stderr, "Failed to allocate XClassHint!\n" );
}
} else {
fprintf( stderr, "Pre-existing XClassHint\n" );
}
XSelectInput (CNFGDisplay, CNFGWindow, KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | ExposureMask | PointerMotionMask | FocusChangeMask );
CNFGWindowGC = XCreateGC(CNFGDisplay, CNFGWindow, 0, 0);
if( CNFGX11ForceNoDecoration )
{
Atom window_type = XInternAtom(CNFGDisplay, "_NET_WM_WINDOW_TYPE", False);
long value = XInternAtom(CNFGDisplay, "_NET_WM_WINDOW_TYPE_SPLASH", False);
XChangeProperty(CNFGDisplay, CNFGWindow, window_type,
XA_ATOM, 32, PropModeReplace, (unsigned char *) &value,1 );
}
CNFGPixmap = XCreatePixmap( CNFGDisplay, CNFGWindow, CNFGWinAtt.width, CNFGWinAtt.height, CNFGWinAtt.depth );
CNFGGC = XCreateGC(CNFGDisplay, CNFGPixmap, 0, 0);
XSetLineAttributes(CNFGDisplay, CNFGGC, 1, LineSolid, CapRound, JoinRound);
CNFGChangeWindowTitle( WindowName );
if( !CNFGWindowInvisible )
XMapWindow(CNFGDisplay, CNFGWindow);
CNFGXIM = XOpenIM(CNFGDisplay, NULL, wm_res_name, wm_res_class);
CNFGXIC = XCreateIC(CNFGXIM, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, NULL);
#ifdef CNFG_HAS_XSHAPE
if( prepare_xshape )
{
xsval.foreground = 1;
xsval.line_width = 1;
xsval.line_style = LineSolid;
xspixmap = XCreatePixmap(CNFGDisplay, CNFGWindow, CNFGWinAtt.width, CNFGWinAtt.height, 1);
xsgc = XCreateGC(CNFGDisplay, xspixmap, 0, &xsval);
XSetLineAttributes(CNFGDisplay, xsgc, 1, LineSolid, CapRound, JoinRound);
}
#endif
}
void CNFGSetupFullscreen( const char * WindowName, int screen_no )
{
#ifdef HAS_XINERAMA
XineramaScreenInfo *screeninfo = NULL;
int screens;
int event_basep, error_basep, a, b;
CNFGDisplay = XOpenDisplay(NULL);
CNFGScreen = XDefaultScreen(CNFGDisplay);
int xpos, ypos;
if (!XShapeQueryExtension(CNFGDisplay, &event_basep, &error_basep)) {
fprintf( stderr, "X-Server does not support shape extension\n" );
exit( 1 );
}
CNFGVisual = DefaultVisual(CNFGDisplay, CNFGScreen);
CNFGVisualID = 0;
CNFGWinAtt.depth = DefaultDepth(CNFGDisplay, CNFGScreen);
#ifdef CNFGOGL
CNFGGLXSetup();
#endif
if (XineramaQueryExtension(CNFGDisplay, &a, &b ) &&
(screeninfo = XineramaQueryScreens(CNFGDisplay, &screens)) &&
XineramaIsActive(CNFGDisplay) && screen_no >= 0 &&
screen_no < screens ) {
CNFGWinAtt.width = screeninfo[screen_no].width;
CNFGWinAtt.height = screeninfo[screen_no].height;
xpos = screeninfo[screen_no].x_org;
ypos = screeninfo[screen_no].y_org;
} else
{
CNFGWinAtt.width = XDisplayWidth(CNFGDisplay, CNFGScreen);
CNFGWinAtt.height = XDisplayHeight(CNFGDisplay, CNFGScreen);
xpos = 0;
ypos = 0;
}
if (screeninfo)
XFree(screeninfo);
XSetWindowAttributes setwinattr;
setwinattr.override_redirect = 1;
setwinattr.save_under = 1;
#ifdef CNFG_HAS_XSHAPE
if (prepare_xshape && !XShapeQueryExtension(CNFGDisplay, &event_basep, &error_basep))
{
fprintf( stderr, "X-Server does not support shape extension" );
exit( 1 );
}
setwinattr.event_mask = 0;
#else
//This code is probably made irrelevant by the XSetEventMask in InternalLinkScreenAndGo, if this code is not found needed by 2019-12-31, please remove.
//setwinattr.event_mask = StructureNotifyMask | SubstructureNotifyMask | ExposureMask | ButtonPressMask | ButtonReleaseMask | ButtonPressMask | PointerMotionMask | ButtonMotionMask | EnterWindowMask | LeaveWindowMask |KeyPressMask |KeyReleaseMask | SubstructureNotifyMask | FocusChangeMask;
#endif
setwinattr.border_pixel = 0;
setwinattr.colormap = XCreateColormap( CNFGDisplay, RootWindow(CNFGDisplay, 0), CNFGVisual, AllocNone);
CNFGWindow = XCreateWindow(CNFGDisplay, XRootWindow(CNFGDisplay, CNFGScreen),
xpos, ypos, CNFGWinAtt.width, CNFGWinAtt.height,
0, CNFGWinAtt.depth, InputOutput, CNFGVisual,
CWBorderPixel/* | CWEventMask */ | CWOverrideRedirect | CWSaveUnder | CWColormap,
&setwinattr);
FullScreen = 1;
InternalLinkScreenAndGo( WindowName );
#ifdef CNFGOGL
glXMakeCurrent( CNFGDisplay, CNFGWindow, CNFGCtx );
#endif
#ifdef CNFG_BATCH
#ifndef CNFGCONTEXTONLY
CNFGSetupBatchInternal();
#endif
#endif
#else
CNFGSetup( WindowName, 640, 480 );
#endif
}
void CNFGTearDown()
{
HandleDestroy();
if( xi ) free( xi );
if ( CNFGClassHint ) XFree( CNFGClassHint );
if ( CNFGGC ) XFreeGC( CNFGDisplay, CNFGGC );
if ( CNFGWindowGC ) XFreeGC( CNFGDisplay, CNFGWindowGC );
if ( CNFGDisplay ) XCloseDisplay( CNFGDisplay );
if ( CNFGXIC ) XDestroyIC( CNFGXIC );
if ( CNFGXIM ) XCloseIM( CNFGXIM );
#ifdef CNFGOGL
if ( CNFGGLXFBConfigs ) XFree( CNFGGLXFBConfigs );
CNFGGLXFBConfigs = NULL;
#endif
CNFGDisplay = NULL;
CNFGWindowGC = CNFGGC = NULL;
CNFGClassHint = NULL;
CFNGWMDeleteWindow = None;
}
int CNFGSetupWMClass( const char * WindowName, int w, int h , char * wm_res_name_ , char * wm_res_class_ )
{
wm_res_name = wm_res_name_;
wm_res_class = wm_res_class_;
return CNFGSetup( WindowName, w, h);
}
int CNFGSetup( const char * WindowName, int w, int h )
{
CNFGDisplay = XOpenDisplay(NULL);
if ( !CNFGDisplay ) {
fprintf( stderr, "Could not get an X Display.\n%s",
"Are you in text mode or using SSH without X11-Forwarding?\n" );
exit( 1 );
}
atexit( CNFGTearDown );
CNFGScreen = DefaultScreen(CNFGDisplay);
CNFGDepth = DefaultDepth(CNFGDisplay, CNFGScreen);
CNFGVisual = DefaultVisual(CNFGDisplay, CNFGScreen);
CNFGVisualID = 0;
Window wnd = DefaultRootWindow( CNFGDisplay );
#ifdef CNFGOGL
CNFGGLXSetup( );
#endif
XSetWindowAttributes attr;
attr.background_pixel = 0;
attr.colormap = XCreateColormap( CNFGDisplay, wnd, CNFGVisual, AllocNone);
if( w > 0 && h > 0 )
CNFGWindow = XCreateWindow(CNFGDisplay, wnd, 1, 1, w, h, 0, CNFGDepth, InputOutput, CNFGVisual, CWBackPixel | CWColormap, &attr );
else
{
if( w < 0 ) w = -w;
if( h < 0 ) h = -h;
CNFGWindow = XCreateWindow(CNFGDisplay, wnd, 1, 1, w, h, 0, CNFGDepth, InputOutput, CNFGVisual, CWBackPixel | CWColormap, &attr );
CNFGWindowInvisible = 1;
}
InternalLinkScreenAndGo( WindowName );
//Not sure of the purpose of this code - if it's still commented out after 2019-12-31 and no one knows why, please delete it.
CFNGWMDeleteWindow = XInternAtom( CNFGDisplay, "WM_DELETE_WINDOW", False );
XSetWMProtocols( CNFGDisplay, CNFGWindow, &CFNGWMDeleteWindow, 1 );
// X11 doesn't have a concept of a hidden cursor. Make a blank cursor as a substitute
XColor col = { 0 };
Pixmap blank = XCreateBitmapFromData( CNFGDisplay, CNFGWindow, (char*)(&col), 1, 1 );
CNFGCursors[CNFG_CURSOR_HIDDEN] = XCreatePixmapCursor( CNFGDisplay, blank, blank, &col, &col, 0, 0 );
XFreePixmap( CNFGDisplay, blank );
#ifdef CNFGOGL
glXMakeCurrent( CNFGDisplay, CNFGWindow, CNFGCtx );
#endif
#ifdef CNFG_BATCH
#ifndef CNFGCONTEXTONLY
CNFGSetupBatchInternal();
#endif
#endif
return 0;
}
int CNFGHandleInput()
{
if( !CNFGWindow ) return 0;
static int ButtonsDown;
XEvent report;
int bKeyDirection = 1;
while( XPending( CNFGDisplay ) )
{
XNextEvent( CNFGDisplay, &report );
bKeyDirection = 1;
switch (report.type)
{
case NoExpose:
break;
case Expose:
XGetWindowAttributes( CNFGDisplay, CNFGWindow, &CNFGWinAtt );
if( CNFGPixmap ) XFreePixmap( CNFGDisplay, CNFGPixmap );
CNFGPixmap = XCreatePixmap( CNFGDisplay, CNFGWindow, CNFGWinAtt.width, CNFGWinAtt.height, CNFGWinAtt.depth );
if( CNFGGC ) XFreeGC( CNFGDisplay, CNFGGC );
CNFGGC = XCreateGC(CNFGDisplay, CNFGPixmap, 0, 0);
CNFGLastScancode = 0;
HandleKey( CNFG_X11_EXPOSE, 0 );
break;
case KeyRelease:
{
bKeyDirection = 0;
//Tricky - handle key repeats cleanly.
if( XPending( CNFGDisplay ) )
{
XEvent nev;
XPeekEvent( CNFGDisplay, &nev );
if (nev.type == KeyPress && nev.xkey.time == report.xkey.time && nev.xkey.keycode == report.xkey.keycode )
bKeyDirection = 2;
}
}
case KeyPress:
g_x_global_key_state = report.xkey.state;
g_x_global_shift_key = XLookupKeysym(&report.xkey, 1);
CNFGLastScancode = report.xkey.keycode;
// Chars should ONLY be handled on Key Press
if (report.type == KeyPress) {
char buf[8] = {0};
if (Xutf8LookupString(CNFGXIC, &report.xkey, buf, 8, NULL, NULL)) CNFGLastCharacter = *((int*)buf);
else CNFGLastCharacter = 0;
}
KeySym sym = XLookupKeysym(&report.xkey, 0);
HandleKey( sym, bKeyDirection );
// Don't confuse the program when CNFGLastCharacter is set on KeyRelease.
// Since we shouldn't be using CNFGLastCharacter outside HandleKey anyways, just clear it here.
CNFGLastCharacter = 0;
break;
case ButtonRelease:
bKeyDirection = 0;
case ButtonPress:
HandleButton( report.xbutton.x, report.xbutton.y, report.xbutton.button, bKeyDirection );
ButtonsDown = (ButtonsDown & (~(1<<report.xbutton.button))) | ( bKeyDirection << report.xbutton.button );
//Intentionall fall through -- we want to send a motion in event of a button as well.
case MotionNotify:
HandleMotion( report.xmotion.x, report.xmotion.y, ButtonsDown>>1 );
break;
case FocusIn:
case FocusOut:
CNFGLastScancode = 0;
HandleKey( CNFG_KEY_FOCUS, report.type == FocusIn );
break;
case ClientMessage:
if ( report.xclient.data.l[0] == CFNGWMDeleteWindow )
return 0;
break;
default:
break;
//printf( "Event: %d\n", report.type );
}
}
return 1;
}
#ifdef CNFGOGL
void CNFGSetVSync( int vson )
{
void (*glfn)( int );
void (*glfnXE)( Display *, GLXDrawable, int );
glfn = (void (*)( int ))CNFGGetExtension( "glXSwapIntervalMESA" ); if( glfn ) glfn( vson );
glfn = (void (*)( int ))CNFGGetExtension( "glXSwapIntervalSGI" ); if( glfn ) glfn( vson );
glfnXE = (void (*)( Display *, GLXDrawable, int ))CNFGGetExtension( "glXSwapIntervalEXT" ); if( glfn ) glfnXE( CNFGDisplay, CNFGWindow, vson );
}
#ifdef CNFGRASTERIZER
void CNFGSwapBuffersInternal()
#else
void CNFGSwapBuffers()
#endif
{
if( CNFGWindowInvisible ) return;
#ifndef CNFGRASTERIZER
#ifndef CNFGCONTEXTONLY
CNFGFlushRender();
#endif
#endif
#ifdef CNFG_HAS_XSHAPE
if( taint_shape )
{
XShapeCombineMask(CNFGDisplay, CNFGWindow, ShapeBounding, 0, 0, xspixmap, ShapeSet);
taint_shape = 0;
}
#endif //CNFG_HAS_XSHAPE
glXSwapBuffers( CNFGDisplay, CNFGWindow );
#ifdef FULL_SCREEN_STEAL_FOCUS
if( FullScreen )
XSetInputFocus( CNFGDisplay, CNFGWindow, RevertToParent, CurrentTime );
#endif //FULL_SCREEN_STEAL_FOCUS
}
#else //CNFGOGL
#ifndef CNFGRASTERIZER
void CNFGBlitImage( uint32_t * data, int x, int y, int w, int h )
{
static int lw, lh;
if( lw != w || lh != h || !xi )
{
if( xi ) free( xi );
xi = XCreateImage(CNFGDisplay, CNFGVisual, CNFGDepth, ZPixmap, 0, (char*)data, w, h, 32, w*4 );
lw = w;
lh = h;
}
xi->data = (char*)data;
//Draw image to pixmap (not a screen flip)
XPutImage(CNFGDisplay, CNFGPixmap, CNFGGC, xi, 0, 0, x, y, w, h );
}
#endif
void CNFGUpdateScreenWithBitmap( uint32_t * data, int w, int h )
{
static int lw, lh;
if( lw != w || lh != h || !xi )
{
if( xi ) free( xi );
xi = XCreateImage(CNFGDisplay, CNFGVisual, CNFGDepth, ZPixmap, 0, (char*)data, w, h, 32, w*4 );
lw = w;
lh = h;
}
xi->data = (char*)data;
//Directly write image to screen (effectively a flip)
XPutImage(CNFGDisplay, CNFGWindow, CNFGWindowGC, xi, 0, 0, 0, 0, w, h );
}
#endif //CNFGOGL
#if !defined( CNFGOGL)
#define AGLF(x) x
#else
#define AGLF(x) static inline BACKEND_##x
#endif
#if defined( CNFGRASTERIZER )
#include "CNFGRasterizer.c"
#undef AGLF
#define AGLF(x) static inline BACKEND_##x
#endif
uint32_t AGLF(CNFGColor)( uint32_t RGB )
{
CNFGLastColor = RGB;
unsigned char red = ( RGB >> 24 ) & 0xFF;
unsigned char grn = ( RGB >> 16 ) & 0xFF;
unsigned char blu = ( RGB >> 8 ) & 0xFF;
unsigned long color = (red<<16)|(grn<<8)|(blu);
XSetForeground(CNFGDisplay, CNFGGC, color);
return color;
}
void AGLF(CNFGClearFrame)()
{
XGetWindowAttributes( CNFGDisplay, CNFGWindow, &CNFGWinAtt );
XSetForeground(CNFGDisplay, CNFGGC, CNFGColor(CNFGBGColor) );
XFillRectangle(CNFGDisplay, CNFGPixmap, CNFGGC, 0, 0, CNFGWinAtt.width, CNFGWinAtt.height );
}
void AGLF(CNFGSwapBuffers)()
{
#ifdef CNFG_HAS_XSHAPE
if( taint_shape )
{
XShapeCombineMask(CNFGDisplay, CNFGWindow, ShapeBounding, 0, 0, xspixmap, ShapeSet);
taint_shape = 0;
}
#endif
XCopyArea(CNFGDisplay, CNFGPixmap, CNFGWindow, CNFGWindowGC, 0,0,CNFGWinAtt.width,CNFGWinAtt.height,0,0);
XFlush(CNFGDisplay);
#ifdef FULL_SCREEN_STEAL_FOCUS
if( FullScreen )
XSetInputFocus( CNFGDisplay, CNFGWindow, RevertToParent, CurrentTime );
#endif
}
void AGLF(CNFGTackSegment)( short x1, short y1, short x2, short y2 )
{
if( x1 == x2 && y1 == y2 )
{
//On some targets, zero-length lines will not show up.
//This is tricky - since this will also cause more draw calls for points on systems like GLAMOR.
XDrawPoint( CNFGDisplay, CNFGPixmap, CNFGGC, x2, y2 );
}
else
{
//XXX HACK! See discussion here: https://github.com/cntools/cnping/issues/68
XDrawLine( CNFGDisplay, CNFGPixmap, CNFGGC, x1, y1, x2, y2 );
XDrawLine( CNFGDisplay, CNFGPixmap, CNFGGC, x2, y2, x1, y1 );
}
}
void AGLF(CNFGTackPixel)( short x1, short y1 )
{
XDrawPoint( CNFGDisplay, CNFGPixmap, CNFGGC, x1, y1 );
}
void AGLF(CNFGTackRectangle)( short x1, short y1, short x2, short y2 )
{
XFillRectangle(CNFGDisplay, CNFGPixmap, CNFGGC, x1, y1, x2-x1, y2-y1 );
}
void AGLF(CNFGTackPoly)( RDPoint * points, int verts )
{
XFillPolygon(CNFGDisplay, CNFGPixmap, CNFGGC, (XPoint *)points, verts, Convex, CoordModeOrigin );
}
void AGLF(CNFGInternalResize)( short x, short y ) { }
void AGLF(CNFGSetLineWidth)( short width )
{
XSetLineAttributes(CNFGDisplay, CNFGGC, width, LineSolid, CapRound, JoinRound);
}
#endif // _CNFGXDRIVER_C

View file

@ -0,0 +1,23 @@
The MIT License (MIT)
Copyright (c) 2010-2020 <>< CNLohr
Copyright (c) 2004-2008 Joshua Allen (portions of CNFG3D)
Copyright (c) 2011-2013 Luc Verhaegen <libv@skynet.be> (Android)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,69 @@
all : rawdraw simple rawdraw_sf.h
#for X11 consider: xorg-dev
#for X11, you will need: libx-dev
#for full screen you'll need: libxinerama-dev libxext-dev
#for OGL You'll need: mesa-common-dev libglu1-mesa-dev
#-DCNFGRASTERIZER
# and
#-CNFGOGL
# are incompatible.
MINGW32:=/usr/bin/i686-w64-mingw32
rawdraw-clang.exe: rawdraw.c
clang rawdraw.c -o rawdraw-clang.exe -g -O1 -Irawdraw -lopengl32 -lgdi32 -luser32
rawdrawogl-clang.exe: rawdraw.c
clang rawdraw.c -o rawdrawogl-clang.exe -g -O1 -Irawdraw -DCNFGOGL -lopengl32 -lgdi32 -luser32
rawdraw.exe : rawdraw.c
$(MINGW32)gcc -g -m32 -o $@ $^ -lgdi32
rawdrawogl.exe : rawdraw.c
$(MINGW32)gcc -g -m32 -o $@ $^ -lgdi32 -DCNFGOGL -lopengl32
rawdraw_egl : rawdraw.c
gcc -o $@ $^ -lMali -lpthread -lm -O3
simple : simple.c
gcc -o $@ $^ -lX11 -lpthread -lXinerama -lXext -lGL -g -lm -ldl
rawdraw : rawdraw.c
gcc -o $@ $^ -lX11 -lpthread -lXinerama -lXext -lGL -g -lm -ldl
rawdraw_ogl : rawdraw.c
gcc -o $@ $^ -lX11 -lpthread -lXinerama -lXext -lGL -g -DCNFGOGL -lm -ldl
osdtest : osdtest.c CNFG.c
gcc -o $@ $^ -lX11 -lpthread -lXinerama -lXext -DCNFG_HAS_XINERAMA -DCNFG_HAS_XSHAPE -lm -ldl
ogltest : ogltest.c CNFG.c
gcc -o $@ $^ -lX11 -lXinerama -lGL -DCNFGOGL -lm
rawdraw_http : rawdraw.c tools/rawdraw_http_page.h
$(CC) -o $@ $< -DCNFGHTTP -lm -ldl -Os -s
tools/binary_to_buffer : tools/binary_to_buffer.c
$(CC) -o $@ $^
tools/rawdraw_http_page.h : tools/rawdraw_http_files/index.html tools/binary_to_buffer
cat $< | gzip -9 | tools/binary_to_buffer webpage_buffer > $@
ogltest.exe : ogltest.c CNFG.c
$(MINGW32)gcc -o $@ $^ -lgdi32 -lkernel32 -lopengl32 -DCNFGOGL -lm
tools/single_file_creator : tools/single_file_creator.c
gcc -o $@ $^
rawdraw_sf.h : tools/single_file_creator CNFG.h tools/rawdraw_http_page.h
echo "//This file was automatically generated by Makefile at https://github.com/cntools/rawdraw" > $@
echo "//This single-file feature is still in early alpha testing." >> $@f
echo "//Generated from files git hash $(shell git rev-parse HEAD) on $(shell date) (This is not the git hash of this file)" >> $@
./tools/single_file_creator CNFG.h CNFG.c CNFGWinDriver.c CNFGEGLLeanAndMean.c CNFGRasterizer.c CNFGEGLDriver.c CNFGWASMDriver.c CNFGXDriver.c CNFGFunctions.c CNFG3D.c CNFGAndroid.h CNFGHTTP.c tools/rawdraw_http_page.h >> $@
clean :
rm -rf *.o *~ simple simple.exe rawdraw.exe rawdrawogl.exe rawdraw rawdraw_ogl rawdraw_mac rawdraw_mac_soft rawdraw_mac_cg rawdraw_mac_ogl ogltest ogltest.exe rawdraw_egl rawdraw_http rawdraw_sf.h rawdraw_sf.hf tools/single_file_creator tools/binary_to_buffer tools/rawdraw_http_page.h

View file

@ -0,0 +1,229 @@
# CN's Fundamental Graphics Library
An OS-Agnostic (including No OS AT ALL!) drawing system. It includes lines,
boxes, linetext, points, bitmaps.
The drawing method can either be by the OS (i.e. SetPixel in Windows),
rasterized in software (we draw our own lines) or by OpenGL where available.
You can reconfigure it with various `#define`s. It also makes it really easy
to get an OpenGL window on just about any supported platform.
Somewhere unusual and want to open a window and draw stuff? Use rawdraw.
Other packages too big, bulky or include junk you don't want? Use rawdraw.
Want to use OpenGL really quickly? Use rawdraw.
For embedded platforms, `CNFGRASTERIZER` is usually the back end which allows
you to have high level commands on a framebuffer. It's what was used in ESP
Channel 3. You can even play with the rasterizer on every mode except the
Android port by defining it. You can even be twisted on Windows, Linux and
on WASM (Web) and define it.
This is **not** like SDL - some drivers have different quirks, some have
more features implemented than others, and a major goal is not to abstract
very much, but almost to provide an exmaple how to use graphics on a specific
platform.
## Platform statuses
* Windows, 100% Support for CNFGOGL and/or CNFGRASTERIZER, Native (GDI32) does not support alpha-blitting, or variable line width.
* Linux, 100% Support for CNFGOGL and/or CNFGRASTERIZER, Native (X11) does not support alpha-blitting, or variable line thickness
.
* Android, 100% Support for CNFGOGL (It is forced on) CNFGRASTERIZER not allowed.
* WASM, 100% Support for CNFGOGL
* Other EGL (Like Raspberry Pi Raw Metal) Platforms, same as Android, but fixed window size.
* EGL, CNFGOGL represent OpenGL or OpenGL ES depending on platform support.
* Vulkan support in progress.
* CNFG3D Support has been ported with fixed-precision numericals to ESP8266.
* Mac/OSX Support currently unknown. Legacy files moved here: https://github.com/cntools/rawdrawtools/tree/main/attic
## Usage in project
You may want to include CNFG as a submodule or as a single-file header. You can just download rawdraw_sf.h to the path your project is in and `#include` it.
To use it as a single file (rawdraw_sf.h), you can just `wget https://raw.githubusercontent.com/cntools/rawdraw/master/rawdraw_sf.h`
To use it as a submodule you can:
`git submodule add https://github.com/cntools/rawdraw`
paste the command above on your terminal to get a clone of the repository on your system.
note: Incase you get a error saying
```
fatal: not a git repository (or any parent up to mount point /)
Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).
```
use the command `git init` first.
To use CNFG, be sure to do this, or include "CNFG.c" in your project.
```
#define CNFG_IMPLEMENTATION
```
## Usage on Linux
### Prerequisites
Firstly, make sure you have the necessary packages and libraries installed.
To install on **Debian/Ubuntu**:
```
sudo apt-get update
sudo apt-get install xorg-dev libx11-dev libxinerama-dev libxext-dev mesa-common-dev libglu1-mesa-dev
```
To install on **Arch Linux**:
```
sudo pacman -Sy xorg-server-devel libx11 libxinerama libxext mesa glu
```
### Running
To run the sample program in rawdraw:
1. Clone the repository on your system and run `cd rawdraw/`.
2. Run the command `make` (compiles and creates an executable called "simple" from "simple.c").
3. Run the command `./simple` to execute "simple".
## Example program
A more comprehensive example can be found in `rawdraw.c`, but a basic example
is as follows:
```c
//Make it so we don't need to include any other C files in our build.
#define CNFG_IMPLEMENTATION
#include "rawdraw_sf.h"
void HandleKey( int keycode, int bDown ) { }
void HandleButton( int x, int y, int button, int bDown ) { }
void HandleMotion( int x, int y, int mask ) { }
int HandleDestroy() { return 0; }
int main()
{
CNFGSetup( "Example App", 1024, 768 );
while(CNFGHandleInput())
{
CNFGBGColor = 0x000080ff; //Dark Blue Background
short w, h;
CNFGClearFrame();
CNFGGetDimensions( &w, &h );
//Change color to white.
CNFGColor( 0xffffffff );
CNFGPenX = 1; CNFGPenY = 1;
CNFGDrawText( "Hello, World", 2 );
//Draw a white pixel at 30, 30
CNFGTackPixel( 30, 30 );
//Draw a line from 50,50 to 100,50
CNFGTackSegment( 50, 50, 100, 50 );
//Dark Red Color Select
CNFGColor( 0x800000FF );
//Draw 50x50 box starting at 100,50
CNFGTackRectangle( 100, 50, 150, 100 );
//Bright Purple Select
CNFGColor( 0x800000FF );
//Draw a triangle
RDPoint points[3] = { { 30, 36}, {20, 50}, { 40, 50 } };
CNFGTackPoly( points, 3 );
//Draw a bunch of random pixels as a blitted image.
{
static uint32_t data[64*64];
int x, y;
for( y = 0; y < 64; y++ ) for( x = 0; x < 64; x++ )
data[x+y*64] = 0xff | (rand()<<8);
CNFGBlitImage( data, 150, 30, 64, 64 );
}
//Display the image and wait for time to display next frame.
CNFGSwapBuffers();
}
}
```
## Building
Windows compile:
Build with clang
```
clang simple.c -o simple -Irawdraw -lopengl32 -lgdi32 -luser32
```
Build with TCC
```
C:\tcc\tcc simple.c -Irawdraw -lopengl32 -lgdi32 -luser32 C:\windows\system32\msvcrt.dll
```
Linux compile:
```
gcc -o simple simple.c -lm -lX11 -lGL
```
Note, with the STB-style header, you don't need to
`#define CNFG_IMPLEMENTATION` anywhere in your code, and instead can also
compile in `cnfg.c`
## Configurations
Rawdraw is a very disjoint set of configurations
CNFG Configuration options include:
* `CNFG_IMPLEMENTATION` = Include code for implementation.
* `__android__` = build Android port
* `WINDOWS`, `WIN32`, `WIN64` = Windows build
* `CNFGOGL` = Make underlying functions use OpenGL if possible.
* `CNFGRASTERIZER` = Make the underlying graphics engine rasterized functions (software rendering)
* `CNFG3D` = Include CNFG3D with this rawdraw build. This provides *rasterized* graphics functions. Only use this option with the rasterizer.
Platform-Specific
* `HAS_XSHAPE` = Include extra functions for handling on-screen display functionality in X11.
* `HAS_XINERAMA` = Use X11's Xinerama to handle full-screen more cleanly.
Flags you will probably never want to use:
* `EGL_LEAN_AND_MEAN` = Bare bones EGL Driver
## Extra goodies
### RDUI
RDUI is mainted by Overlisted and contains a lot of extra features for rawdraw. You can check it out here: https://git.overlisted.net/me/rdui/
### chew.h
You can copy-and-paste `chew.c` and `chew.h` into your program for a single-file compile-only
version of GLEW. This is WAY BETTER than having to deal with the complicated system they have
set up, but it doesn't include all the functions. You can easily add OpenGL functions to it.
It provides a very easy-to-use interface for OpenGL Extensions.
### os_generic.h
os_generic is a platform independent way of creating threads, managing TLS, mutices,
semaphores as well as getting the current time. It has the following configuration options:
`OSG_NO_IMPLEMENTATION` - Do not include implementation as static functions.
`OSG_PREFIX` - Override function prefix
`OSG_NOSTATIC` - Do not declare the functions as static.
### TextTool/Text.c etc...
There is a sister repository, https://github.com/cntools/rawdrawtools which houses a text tool.
## Warnings
* OSX Support has been moved to attic.
* No support for openGL 1.0. If your hardware doesn't support openGL 2.0 or newer, disable
openGL support. (If you are on desktop, it probably does, you just need to install updated
drivers.)

View file

@ -0,0 +1,83 @@
#if defined( WIN32 ) || defined( WINDOWS ) || defined( WIN64 )
#include <windows.h>
#else
#include <dlfcn.h>
#endif
#include <stdio.h>
#include "chew.h"
#define TABLEONLY
#undef CHEWTYPEDEF
#undef CHEWTYPEDEF2
#define CHEWTYPEDEF( ret, name, retcmd, parameters, ... ) name##_t name##fnptr;
#define CHEWTYPEDEF2( ret, name, usename, retcmd, parameters, ... ) usename##_t usename##fnptr;
#include "chew.h"
#define TABLEONLY
#undef CHEWTYPEDEF
#undef CHEWTYPEDEF2
#define CHEWTYPEDEF( ret, name, retcmd, parameters, ... ) ret name( __VA_ARGS__ ) { retcmd name##fnptr parameters; }
#define CHEWTYPEDEF2( ret, name, usename, retcmd, parameters, ... ) ret usename( __VA_ARGS__ ) { retcmd usename##fnptr parameters ; }
#include "chew.h"
#define TABLEONLY
#undef CHEWTYPEDEF
#undef CHEWTYPEDEF2
#define CHEWTYPEDEF( ret, name, retcmd, parameters, ... ) #name,
#define CHEWTYPEDEF2( ret, name, usename, retcmd, parameters, ... ) #name,
const char * symnames[] = {
#include "chew.h"
};
#define TABLEONLY
#undef CHEWTYPEDEF
#undef CHEWTYPEDEF2
#define CHEWTYPEDEF( ret, name, retcmd, parameters, ... ) (void**)&name##fnptr,
#define CHEWTYPEDEF2( ret, name, usename, retcmd, parameters, ... ) (void**)&usename##fnptr,
void ** syms[] = {
#include "chew.h"
};
#if defined( WIN32 ) || defined( WINDOWS ) || defined( WIN64 )
//From https://www.khronos.org/opengl/wiki/Load_OpenGL_Functions
void * chewGetProcAddress(const char *name)
{
void *p = (void *)wglGetProcAddress(name);
if(p == 0 ||
(p == (void*)0x1) || (p == (void*)0x2) || (p == (void*)0x3) ||
(p == (void*)-1) )
{
static HMODULE module;
if( !module ) module = LoadLibraryA("opengl32.dll");
p = (void *)GetProcAddress(module, name);
}
return p;
}
#else
void * chewGetProcAddress(const char *name)
{
//Tricky use RTLD_NEXT first so we don't accidentally link against ourselves.
void * v1 = dlsym( (void*)((intptr_t)-1) /*RTLD_NEXT = -1*/ /*RTLD_DEFAULT = 0*/, name );
//printf( "%s = %p\n", name, v1 );
if( !v1 ) v1 = dlsym( 0, name );
return v1;
}
#endif
void chewInit() {
int i;
for( i = 0; i < sizeof(symnames) / sizeof(symnames[0]); i++ )
{
void * v = *syms[i] = (void*)chewGetProcAddress( symnames[i] );
if( !v ) fprintf( stderr, "Warning: Can't find \"gl%s\"\n", symnames[i] );
//else printf( "%p = %s\n", v, symnames[i] );
}
}

View file

@ -0,0 +1,184 @@
#if !defined( _CHEW_H ) || defined( TABLEONLY )
#define _CHEW_H
#ifndef TABLEONLY
#if defined( WINDOWS ) || defined( _WINDOWS ) || defined( WIN32 ) || defined( WIN64 )
#ifdef __TINYC__
#define GLsizeiptr intptr_t
#define GLintptr intptr_t
#endif
//#include <windows.h>
#include <stdint.h>
#else
//Include OpenGL or something maybe?
#endif
#endif
#if defined( WIN32 ) || defined( WINDOWS )
#define STDCALL __stdcall
#else
#define STDCALL
#endif
#ifdef __cplusplus
#ifndef TABLEONLY
extern "C" {
#endif
#endif
#ifndef TABLEONLY
#ifdef EGL_LEAN_AND_MEAN
#include <GLES/gl.h>
#include <GLES3/gl3.h>
#else
#include <GL/gl.h>
#endif
#ifdef NO_OPENGL_HEADERS
#include "chewtypes.h"
#endif
#ifndef chew_FUN_EXPORT
#define chew_FUN_EXPORT extern
#endif
#define CHEWTYPEDEF( ret, name, retcmd, parameters, ... ) \
typedef ret (STDCALL *name##_t)( __VA_ARGS__ ); \
chew_FUN_EXPORT name##_t name##fnptr; \
chew_FUN_EXPORT ret name( __VA_ARGS__ );
#define CHEWTYPEDEF2( ret, name, usename, retcmd, parameters, ... ) \
typedef ret (STDCALL *usename##_t)( __VA_ARGS__ ); \
chew_FUN_EXPORT usename##_t usename##fnptr; \
chew_FUN_EXPORT ret usename( __VA_ARGS__ );
#if defined( __TINYC__ ) && ( defined( WINDOWS ) || defined( _WINDOWS ) || defined( WIN32 ) || defined( WIN64 ) )
#undef GLchar
#define GLchar char
#undef GLDEBUGPROC
#define GLDEBUGPROC void*
#endif
#if defined (TCCINSTANCE)
#undef GLvoid
#define GLvoid void
#undef GLintptr
#define GLintptr int*
#undef GLchar
#define GLchar char
#undef GLsizeiptr
#define GLsizeiptr unsigned int
#endif
void chewInit();
void * chewGetProcAddress( const char *name );
#endif
// Add the things you want here; DO NOT put ; at end of line.
CHEWTYPEDEF( void, glGenVertexArrays, , (n,arrays), GLsizei n, GLuint *arrays )
CHEWTYPEDEF( void, glBindVertexArray, , (array), GLuint array )
CHEWTYPEDEF( void, glGenBuffers, , (n,buffers), GLsizei n, GLuint * buffers )
CHEWTYPEDEF( void, glBindBuffer, , (target,buffer), GLenum target, GLuint buffer )
CHEWTYPEDEF( void, glBufferData, , (target,size,data,usage), GLenum target, GLsizeiptr size, const GLvoid * data, GLenum usage )
CHEWTYPEDEF( void, glNamedBufferData, , (buffer,size,data,usage) , GLuint buffer, GLsizeiptr size, const GLvoid *data, GLenum usage )
CHEWTYPEDEF( void, glEnableVertexAttribArray, , (index), GLuint index )
CHEWTYPEDEF( void, glDisableVertexAttribArray, , (index), GLuint index )
CHEWTYPEDEF( void, glEnableVertexArrayAttrib, , (vaobj,index), GLuint vaobj, GLuint index )
CHEWTYPEDEF( void, glDisableVertexArrayAttrib, , (vaobj,index), GLuint vaobj, GLuint index )
CHEWTYPEDEF( void, glVertexAttribPointer, , (index,size,type,normalized,stride,pointer), GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid * pointer )
CHEWTYPEDEF( void, glVertexAttribIPointer, , (index,size,type,stride,pointer), GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid * pointer )
CHEWTYPEDEF( void, glVertexAttribLPointer, , (index,size,type,stride,pointer), GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid * pointer )
CHEWTYPEDEF( void, glBindAttribLocation, , (program,index,name), GLuint program, GLuint index, const GLchar *name )
CHEWTYPEDEF( void, glDeleteVertexArrays, , (n,arrays), GLsizei n, const GLuint *arrays )
CHEWTYPEDEF( void, glDeleteBuffers, , (n,buffers), GLsizei n, const GLuint * buffers )
CHEWTYPEDEF( void, glBufferSubData, , (target,offset,size,data), GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid * data )
CHEWTYPEDEF( void, glNamedBufferSubData, , (buffer,offset,size,data), GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data )
//Already covered in SDL_opengl.h
CHEWTYPEDEF2( void, glActiveTexture, glActiveTextureCHEW, , (texture) , GLenum texture )
CHEWTYPEDEF2( void, glSampleCoverage, glSampleCoverageCHEW, , (value,invert), GLfloat value, GLboolean invert )
#ifndef EGL_LEAN_AND_MEAN
#ifndef TCCINSTANCE
CHEWTYPEDEF( void, glDebugMessageCallback, , (callback,userParam), GLDEBUGPROC callback, const void * userParam )
CHEWTYPEDEF( void, glDebugMessageControl, , (source,type,severity,count,ids,enabled), GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled )
#endif
#endif
CHEWTYPEDEF2( void, glGenerateMipmap, glGenerateMipmapCHEW, , (index), GLuint index )
CHEWTYPEDEF( void, glGenFramebuffers, , (n,framebuffers), GLsizei n, GLuint * framebuffers )
CHEWTYPEDEF( void, glGenRenderbuffers, , (n,renderbuffers), GLsizei n, GLuint * renderbuffers )
CHEWTYPEDEF( void, glBindFramebuffer, , (target,framebuffer), GLenum target, GLuint framebuffer )
CHEWTYPEDEF( void, glBindRenderbuffer, , (target,renderbuffer), GLenum target, GLuint renderbuffer )
CHEWTYPEDEF( void, glRenderbufferStorage, , (target,internalformat,width,height), GLenum target, GLenum internalformat, GLsizei width, GLsizei height )
CHEWTYPEDEF( void, glRenderbufferStorageMultisample, , (target,samples,internalformat,width,height), GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height )
CHEWTYPEDEF( void, glNamedRenderbufferStorageMultisample, , (renderbuffer,samples,internalformat,width,height), GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height )
CHEWTYPEDEF( void, glFramebufferRenderbuffer, ,(target,attachment,renderbuffertarget,renderbuffer), GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer )
CHEWTYPEDEF( void, glTexImage2DMultisample, ,(target,samples,internalformat, width, height, fixedsamplelocations),GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations )
CHEWTYPEDEF( void, glFramebufferTexture2D, ,(target,attachment, textarget, texture, level) , GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level )
CHEWTYPEDEF( void, glBlitFramebuffer, , (srcX0, srcY0, srcX1, srcY1,dstX0, dstY0,dstX1, dstY1,mask, filter) , GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter )
CHEWTYPEDEF( void, glBlitNamedFramebuffer, , (readFramebuffer, drawFramebuffer, srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter), GLuint readFramebuffer, GLuint drawFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter )
CHEWTYPEDEF( void, glDeleteFramebuffers, , (n,framebuffers), GLsizei n, const GLuint * framebuffers )
CHEWTYPEDEF( void, glDeleteRenderbuffers, , (n,renderbuffers), GLsizei n, const GLuint * renderbuffers )
CHEWTYPEDEF( GLenum, glCheckFramebufferStatus, return, (target) , GLenum target )
CHEWTYPEDEF( GLenum, glCheckNamedFramebufferStatus, return, (framebuffer,target), GLuint framebuffer, GLenum target )
CHEWTYPEDEF( void, glFramebufferTexture, , (target,attachment,texture,level), GLenum target, GLenum attachment, GLuint texture, GLint level )
CHEWTYPEDEF( GLuint, glCreateProgram, return, () , void )
CHEWTYPEDEF( GLuint, glCreateShader, return, (e), GLenum e )
#ifndef EGL_LEAN_AND_MEAN
CHEWTYPEDEF( void, glShaderSource, , (shader,count,string,length), GLuint shader, GLsizei count, const GLchar **string, const GLint *length )
#endif
CHEWTYPEDEF( void, glCompileShader, ,(shader), GLuint shader )
CHEWTYPEDEF( void, glGetShaderiv, , (shader,pname,params), GLuint shader, GLenum pname, GLint *params )
CHEWTYPEDEF( void, glGetShaderInfoLog , , (shader,maxLength, length, infoLog), GLuint shader, GLsizei maxLength, GLsizei *length, GLchar *infoLog )
CHEWTYPEDEF( void, glDeleteProgram, , (program), GLuint program )
CHEWTYPEDEF( void, glDeleteShader, , (shader), GLuint shader )
CHEWTYPEDEF( void, glAttachShader, , (program,shader), GLuint program, GLuint shader )
CHEWTYPEDEF( void, glLinkProgram, , (program), GLuint program )
CHEWTYPEDEF( void, glGetProgramiv, , (program,pname,params), GLuint program, GLenum pname, GLint *params )
CHEWTYPEDEF( void, glUseProgram, , (program), GLuint program )
CHEWTYPEDEF( void, glUniform1f, , (location,v0), GLint location, GLfloat v0 )
CHEWTYPEDEF( void, glUniform2f, , (location,v0,v1), GLint location, GLfloat v0, GLfloat v1 )
CHEWTYPEDEF( void, glUniform3f, , (location,v0,v1,v2), GLint location, GLfloat v0, GLfloat v1, GLfloat v2 )
CHEWTYPEDEF( void, glUniform4f, , (location,v0,v1,v2,v3), GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3 )
CHEWTYPEDEF( void, glUniform1i, , (location,v0), GLint location, GLint v0 )
CHEWTYPEDEF( void, glUniform2i, , (location,v0,v1), GLint location, GLint v0, GLint v1 )
CHEWTYPEDEF( void, glUniform3i, , (location,v0,v1,v2), GLint location, GLint v0, GLint v1, GLint v2 )
CHEWTYPEDEF( void, glUniform4i, , (location,v0,v1,v2,v3), GLint location, GLint v0, GLint v1, GLint v2, GLint v3 )
CHEWTYPEDEF( void, glUniform1fv, , (location,count,value), GLint location, GLsizei count, const GLfloat *value )
CHEWTYPEDEF( void, glUniform2fv, , (location,count,value), GLint location, GLsizei count, const GLfloat *value )
CHEWTYPEDEF( void, glUniform3fv, , (location,count,value), GLint location, GLsizei count, const GLfloat *value )
CHEWTYPEDEF( void, glUniform4fv, , (location,count,value), GLint location, GLsizei count, const GLfloat *value )
CHEWTYPEDEF2( void, glUniform4fv, glUniform4fvCHEW, , (location,count,value), GLint location, GLsizei count, const GLfloat *value )
CHEWTYPEDEF( void, glUniform1iv, , (location,count,value), GLint location, GLsizei count, const GLint *value )
CHEWTYPEDEF( void, glUniform2iv, , (location,count,value), GLint location, GLsizei count, const GLint *value )
CHEWTYPEDEF( void, glUniform3iv, , (location,count,value), GLint location, GLsizei count, const GLint *value )
CHEWTYPEDEF( void, glUniform4iv, , (location,count,value), GLint location, GLsizei count, const GLint *value )
CHEWTYPEDEF( void, glUniformMatrix2fv, ,(location,count,transpose,value) , GLint location, GLsizei count, GLboolean transpose, const GLfloat *value )
CHEWTYPEDEF( void, glUniformMatrix3fv, ,(location,count,transpose,value) , GLint location, GLsizei count, GLboolean transpose, const GLfloat *value )
CHEWTYPEDEF( void, glUniformMatrix4fv, ,(location,count,transpose,value) , GLint location, GLsizei count, GLboolean transpose, const GLfloat *value )
CHEWTYPEDEF( void, glGetProgramInfoLog, , (program,maxLength, length, infoLog), GLuint program, GLsizei maxLength, GLsizei *length, GLchar *infoLog )
CHEWTYPEDEF( GLint, glGetUniformLocation, return, (program,name), GLuint program, const GLchar *name )
CHEWTYPEDEF( void *, glMapBuffer, return, (target,access), GLenum target, GLenum access )
CHEWTYPEDEF( void *, glMapNamedBuffer, return, (buffer,access), GLuint buffer, GLenum access )
CHEWTYPEDEF( void *, glMapBufferRange, return, (buffer,offset,length,access), GLuint buffer, GLintptr offset, GLsizeiptr length, GLbitfield access )
CHEWTYPEDEF( GLboolean, glUnmapBuffer, return, (target), GLenum target )
#ifdef __cplusplus
#ifndef TABLEONLY
};
#endif
#endif
#endif // _CHEW_H

View file

@ -0,0 +1,118 @@
#ifndef _CHEWTYPES_H
#define _CHEWTYPES_H
#include <stdint.h>
typedef int GLfixed;
typedef int GLclampx;
typedef int64_t GLint64;
typedef uint64_t GLuint64;
typedef uint32_t GLuint32;
typedef int32_t GLint32;
typedef uint32_t GLuint;
typedef char GLchar;
typedef struct __GLsync *GLsync;
typedef intptr_t GLsizeiptr;
typedef intptr_t GLintptr;
#ifndef APIENTRY
#define APIENTRY
#endif
#ifndef GLDEBUGPROC
typedef void (APIENTRY *GLDEBUGPROC)(
GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length,
const GLchar *message, const void *userParam);
#endif
#ifndef GL_DEBUG_OUTPUT_SYNCHRONOUS
#define GL_DEBUG_OUTPUT_SYNCHRONOUS 0x8242
#define GL_COMPILE_STATUS 0x8B81
#define GL_LINK_STATUS 0x8B82
#define GL_FRAGMENT_SHADER 0x8B30
#define GL_VERTEX_SHADER 0x8B31
#define GL_INFO_LOG_LENGTH 0x8B84
#define GL_GEOMETRY_SHADER 0x8DD9
#define GL_STATIC_DRAW 0x88E4
#define GL_ARRAY_BUFFER 0x8892
#define GL_DYNAMIC_DRAW 0x88E8
#define GL_ELEMENT_ARRAY_BUFFER 0x8893
#define GL_CLAMP_TO_EDGE 0x812F
#define GL_DEPTH_ATTACHMENT 0x8D00
#define GL_FRAMEBUFFER 0x8D40
#define GL_MULTISAMPLE 0x809D
#define GL_RENDERBUFFER 0x8D41
#define GL_READ_FRAMEBUFFER 0x8CA8
#define GL_DRAW_FRAMEBUFFER 0x8CA9
#define GL_PROGRAM_POINT_SIZE 0x8642
#define GL_SAMPLE_COVERAGE 0x80A0
#define GL_TEXTURE_2D_MULTISAMPLE 0x9100
#define GL_COLOR_ATTACHMENT0 0x8CE0
#define GL_FRAMEBUFFER_COMPLETE 0x8CD5
#define GL_TEXTURE_MAX_LEVEL 0x813D
#define GL_TEXTURE_MAX_ANISOTROPY 0x84FE
#define GL_MAX_TEXTURE_MAX_ANISOTROPY 0x84FF
#define GL_TEXTURE0 0x84C0
#define GL_TEXTURE_BASE_LEVEL 0x813C
#define GL_POINT_SPRITE_OES 0x8861
#define GL_PIXEL_UNPACK_BUFFER 0x88EC
#define GL_STREAM_DRAW 0x88E0
#define GL_PIXEL_PACK_BUFFER 0x88EB
#define GL_READ_ONLY 0x88B8
#define GL_BGRA 0x80E1
#define GL_STREAM_READ 0x88E1
#define GL_TEXTURE_SWIZZLE_RGBA 0x8E46
#define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E
#define GL_READ_ONLY 0x88B8
#define GL_WRITE_ONLY 0x88B9
#define GL_READ_WRITE 0x88BA
#define GL_BUFFER_ACCESS 0x88BB
#define GL_BUFFER_MAPPED 0x88BC
#define GL_BUFFER_MAP_POINTER 0x88BD
#define GL_STREAM_COPY 0x88E2
#define GL_STATIC_DRAW 0x88E4
#define GL_STATIC_READ 0x88E5
#define GL_STATIC_COPY 0x88E6
#define GL_DYNAMIC_DRAW 0x88E8
#define GL_DYNAMIC_READ 0x88E9
#define GL_DYNAMIC_COPY 0x88EA
#define GL_MAP_READ_BIT 0x0001
#define GL_MAP_WRITE_BIT 0x0002
#define GL_MAP_INVALIDATE_RANGE_BIT 0x0004
#define GL_MAP_INVALIDATE_BUFFER_BIT 0x0008
#define GL_MAP_FLUSH_EXPLICIT_BIT 0x0010
#define GL_MAP_UNSYNCHRONIZED_BIT 0x0020
#define GL_VERTEX_PROGRAM_POINT_SIZE 0x8642
#define GL_DEPTH_CLAMP 0x864F
#define GL_COLOR_ATTACHMENT0_EXT 0x8CE0
#define GL_COLOR_ATTACHMENT1_EXT 0x8CE1
#define GL_COLOR_ATTACHMENT2_EXT 0x8CE2
#define GL_COLOR_ATTACHMENT3_EXT 0x8CE3
#define GL_COLOR_ATTACHMENT4_EXT 0x8CE4
#define GL_COLOR_ATTACHMENT5_EXT 0x8CE5
#define GL_COLOR_ATTACHMENT6_EXT 0x8CE6
#define GL_COLOR_ATTACHMENT7_EXT 0x8CE7
#define GL_COLOR_ATTACHMENT8_EXT 0x8CE8
#define GL_POINT_SPRITE 0x8861
#define GL_INVALID_FRAMEBUFFER_OPERATION 0x0506
#define GL_R32F 0x822E
#define GL_RG32F 0x8230
#define GL_RGB32F 0x8815
#define GL_RGBA32F 0x8814
#define GL_R8 0x8229
#define GL_RG8 0x822B
#define GL_RG 0x8227
#endif
#endif

View file

@ -0,0 +1,27 @@
all : fontsize fontsize_ogl
#for X11 consider: xorg-dev
#for X11, you will need: libx-dev
#for full screen you'll need: libxinerama-dev libxext-dev
#for OGL You'll need: mesa-common-dev libglu1-mesa-dev
#-DCNFGRASTERIZER
# and
#-CNFGOGL
# are incompatible.
MINGW32:=/usr/bin/i686-w64-mingw32-
fontsize : fontsize.c
gcc -o $@ $^ -lX11 -lm -lpthread -lXinerama -lXext -lGL -g -DCNFGRASTERIZER -Wall
fontsize_ogl : fontsize.c
gcc -o $@ $^ -lX11 -lm -lpthread -lXinerama -lXext -lGL -g -lX11 -lXinerama -lGL -DCNFGOGL -Wall
fontsize.exe : fontsize.c
$(MINGW32)gcc -m32 -o $@ $^ -lgdi32 $(CFLAGS)
clean :
rm -rf *.o *~ rawdraw.exe rawdraw ontop rawdraw_mac rawdraw_mac_soft rawdraw_mac_cg rawdraw_mac_ogl ogltest ogltest.exe rawdraw_egl fontsize fontsize_ogl fontsize.exe

View file

@ -0,0 +1,77 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define CNFG_IMPLEMENTATION
#include "../CNFG.h"
#include "../os_generic.h"
#if defined( CNFGOGL )
#define TITLESTRING "Fontsize test with OpenGL"
#elif defined( CNFGRASTERIZER )
#define TITLESTRING "Fontsize test with Rasterizer"
#else
#define TITLESTRING "Fontsize test with Unknown"
#endif
void HandleKey( int keycode, int bDown )
{
if( keycode == CNFG_KEY_ESCAPE ) exit( 0 );
printf( "Key: %d -> %d\n", keycode, bDown );
}
void HandleButton( int x, int y, int button, int bDown )
{
printf( "Button: %d,%d (%d) -> %d\n", x, y, button, bDown );
}
void HandleMotion( int x, int y, int mask )
{
// printf( "Motion: %d,%d (%d)\n", x, y, mask );
}
int HandleDestroy()
{
printf( "Destroying\n" );
exit(10);
return 0;
}
int main()
{
int i;
CNFGBGColor = 0x800000;
// Deprecated CNFGDialogColor = 0x444444;
CNFGSetup( TITLESTRING, 1220, 480 );
while (CNFGHandleInput()) {
CNFGClearFrame();
CNFGColor( 0xffffff );
for( i = 1; i < 5; i++ )
{
int c;
char tw[2] = { 0, 0 };
for( c = 0; c < 256; c++ )
{
tw[0] = c;
CNFGPenX = ( c % 16 ) * 16 + 5 + (i - 1) * 320;
CNFGPenY = ( c / 16 ) * 18 + 5;
CNFGDrawText( tw, i );
}
}
CNFGPenX = 20;
CNFGPenY = 300;
for( i = 1; i < 7; i++) {
CNFGPenY += i * 6;
CNFGDrawText("A quick brown fox jumps over the lazy dog.", i);
}
CNFGSwapBuffers();
OGUSleep( (int)( 0.5 * 1000000 ) );
}
}

View file

@ -0,0 +1,56 @@
#include <GL/gl.h>
#include <stdio.h>
#include <stdlib.h>
#include "CNFG.h"
void HandleKey( int keycode, int bDown )
{
printf( "Key: %d %d\n", keycode, bDown );
}
void HandleButton( int x, int y, int button, int bDown )
{
}
void HandleMotion( int x, int y, int mask )
{
}
int HandleDestroy()
{
exit(10);
return 0;
}
int main()
{
short w, h;
int frameno = 0;
CNFGSetup( "Test", 100, 100 );
while(CNFGHandleInput())
{
CNFGClearFrame();
glRotatef( frameno/100.0, 0, 0, 1);
glColor4f( 0, 0, .5, 1 );
glBegin( GL_TRIANGLES );
glVertex3f( -40, -40, 0 );
glVertex3f( 40, 0, 0 );
glVertex3f( 0, 40, 0 );
glEnd();
char stmp[100];
CNFGColor( 0xFFFFFF );
sprintf( stmp, "%d\n", frameno );
CNFGPenX = 10; CNFGPenY = 10;
CNFGDrawText( stmp, 3 );
CNFGTackPixel( 100, 100 );
CNFGSwapBuffers();
frameno++;
}
}

View file

@ -0,0 +1,520 @@
#ifndef _OS_GENERIC_H
#define _OS_GENERIC_H
/*
"osgeneric" Generic, platform independent tool for threads and time.
Geared around Windows and Linux. Designed for operation on MSVC,
TCC, GCC and clang. Others may work.
It offers the following operations:
Delay functions:
void OGSleep( int is );
void OGUSleep( int ius );
Getting current time (may be time from program start, boot, or epoc)
double OGGetAbsoluteTime();
double OGGetFileTime( const char * file );
Thread functions
og_thread_t OGCreateThread( void * (routine)( void * ), void * parameter );
void * OGJoinThread( og_thread_t ot );
void OGCancelThread( og_thread_t ot );
Mutex functions, used for protecting data structures.
(recursive on platforms where available.)
og_mutex_t OGCreateMutex();
void OGLockMutex( og_mutex_t om );
void OGUnlockMutex( og_mutex_t om );
void OGDeleteMutex( og_mutex_t om );
Always a semaphore (not recursive)
og_sema_t OGCreateSema(); //Create a semaphore, comes locked initially.
NOTE: For platform compatibility, max count is 32767
void OGLockSema( og_sema_t os );
int OGGetSema( og_sema_t os ); //if <0 there was a failure.
void OGUnlockSema( og_sema_t os );
void OGDeleteSema( og_sema_t os );
TLS (Thread-Local Storage)
og_tls_t OGCreateTLS();
void OGDeleteTLS( og_tls_t tls );
void OGSetTLS( og_tls_t tls, void * data );
void * OGGetTLS( og_tls_t tls );
You can permute the operations of this file by the following means:
OSG_NO_IMPLEMENTATION
OSG_PREFIX
OSG_NOSTATIC
The default behavior is to do static inline.
Copyright (c) 2011-2012,2013,2016,2018,2019,2020 <>< Charles Lohr
This file may be licensed under the MIT/x11 license, NewBSD or CC0 licenses
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of this file.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
Date Stamp: 2019-09-05 CNL: Allow for noninstantiation and added TLS.
Date Stamp: 2018-03-25 CNL: Switched to header-only format.
*/
#if defined( OSG_NOSTATIC ) && OSG_NOSTATIC != 0
#ifndef OSG_PREFIX
#define OSG_PREFIX
#endif
#ifndef OSG_NO_IMPLEMENTATION
#define OSG_NO_IMPLEMENTATION
#endif
#endif
#ifndef OSG_PREFIX
#ifdef __wasm__
#define OSG_PREFIX
#else
#define OSG_PREFIX static inline
#endif
#endif
//In case you want to hook the closure of a thread, i.e. if your system has thread-local storage.
#ifndef OSG_TERM_THREAD_CODE
#define OSG_TERM_THREAD_CODE
#endif
typedef void* og_thread_t;
typedef void* og_mutex_t;
typedef void* og_sema_t;
typedef void* og_tls_t;
#ifdef __cplusplus
extern "C" {
#endif
OSG_PREFIX void OGSleep( int is );
OSG_PREFIX void OGUSleep( int ius );
OSG_PREFIX double OGGetAbsoluteTime();
OSG_PREFIX double OGGetFileTime( const char * file );
OSG_PREFIX og_thread_t OGCreateThread( void * (routine)( void * ), void * parameter );
OSG_PREFIX void * OGJoinThread( og_thread_t ot );
OSG_PREFIX void OGCancelThread( og_thread_t ot );
OSG_PREFIX og_mutex_t OGCreateMutex();
OSG_PREFIX void OGLockMutex( og_mutex_t om );
OSG_PREFIX void OGUnlockMutex( og_mutex_t om );
OSG_PREFIX void OGDeleteMutex( og_mutex_t om );
OSG_PREFIX og_sema_t OGCreateSema();
OSG_PREFIX int OGGetSema( og_sema_t os );
OSG_PREFIX void OGLockSema( og_sema_t os );
OSG_PREFIX void OGUnlockSema( og_sema_t os );
OSG_PREFIX void OGDeleteSema( og_sema_t os );
OSG_PREFIX og_tls_t OGCreateTLS();
OSG_PREFIX void OGDeleteTLS( og_tls_t key );
OSG_PREFIX void * OGGetTLS( og_tls_t key );
OSG_PREFIX void OGSetTLS( og_tls_t key, void * data );
#ifdef __cplusplus
};
#endif
#ifndef OSG_NO_IMPLEMENTATION
#if defined( WIN32 ) || defined (WINDOWS) || defined( _WIN32)
#define USE_WINDOWS
#endif
#ifdef __cplusplus
extern "C" {
#endif
#ifdef USE_WINDOWS
#include <windows.h>
#include <stdint.h>
OSG_PREFIX void OGSleep( int is )
{
Sleep( is*1000 );
}
OSG_PREFIX void OGUSleep( int ius )
{
Sleep( ius/1000 );
}
OSG_PREFIX double OGGetAbsoluteTime()
{
static LARGE_INTEGER lpf;
LARGE_INTEGER li;
if( !lpf.QuadPart )
{
QueryPerformanceFrequency( &lpf );
}
QueryPerformanceCounter( &li );
return (double)li.QuadPart / (double)lpf.QuadPart;
}
OSG_PREFIX double OGGetFileTime( const char * file )
{
FILETIME ft;
HANDLE h = CreateFile(file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if( h==INVALID_HANDLE_VALUE )
return -1;
GetFileTime( h, 0, 0, &ft );
CloseHandle( h );
return ft.dwHighDateTime + ft.dwLowDateTime;
}
OSG_PREFIX og_thread_t OGCreateThread( void * (routine)( void * ), void * parameter )
{
return (og_thread_t)CreateThread( 0, 0, (LPTHREAD_START_ROUTINE)routine, parameter, 0, 0 );
}
OSG_PREFIX void * OGJoinThread( og_thread_t ot )
{
WaitForSingleObject( ot, INFINITE );
OSG_TERM_THREAD_CODE
CloseHandle( ot );
return 0;
}
OSG_PREFIX void OGCancelThread( og_thread_t ot )
{
OSG_TERM_THREAD_CODE
TerminateThread( ot, 0);
CloseHandle( ot );
}
OSG_PREFIX og_mutex_t OGCreateMutex()
{
return CreateMutex( 0, 0, 0 );
}
OSG_PREFIX void OGLockMutex( og_mutex_t om )
{
WaitForSingleObject(om, INFINITE);
}
OSG_PREFIX void OGUnlockMutex( og_mutex_t om )
{
ReleaseMutex(om);
}
OSG_PREFIX void OGDeleteMutex( og_mutex_t om )
{
CloseHandle( om );
}
OSG_PREFIX og_sema_t OGCreateSema()
{
HANDLE sem = CreateSemaphore( 0, 0, 32767, 0 );
return (og_sema_t)sem;
}
OSG_PREFIX int OGGetSema( og_sema_t os )
{
typedef LONG NTSTATUS;
HANDLE sem = (HANDLE)os;
typedef NTSTATUS (NTAPI *_NtQuerySemaphore)(
HANDLE SemaphoreHandle,
DWORD SemaphoreInformationClass, /* Would be SEMAPHORE_INFORMATION_CLASS */
PVOID SemaphoreInformation, /* but this is to much to dump here */
ULONG SemaphoreInformationLength,
PULONG ReturnLength OPTIONAL
);
typedef struct _SEMAPHORE_BASIC_INFORMATION {
ULONG CurrentCount;
ULONG MaximumCount;
} SEMAPHORE_BASIC_INFORMATION;
static _NtQuerySemaphore NtQuerySemaphore;
SEMAPHORE_BASIC_INFORMATION BasicInfo;
NTSTATUS Status;
if( !NtQuerySemaphore )
{
NtQuerySemaphore = (_NtQuerySemaphore)GetProcAddress (GetModuleHandle ("ntdll.dll"), "NtQuerySemaphore");
if( !NtQuerySemaphore )
{
return -1;
}
}
Status = NtQuerySemaphore (sem, 0 /*SemaphoreBasicInformation*/,
&BasicInfo, sizeof (SEMAPHORE_BASIC_INFORMATION), NULL);
if (Status == ERROR_SUCCESS)
{
return BasicInfo.CurrentCount;
}
return -2;
}
OSG_PREFIX void OGLockSema( og_sema_t os )
{
WaitForSingleObject( (HANDLE)os, INFINITE );
}
OSG_PREFIX void OGUnlockSema( og_sema_t os )
{
ReleaseSemaphore( (HANDLE)os, 1, 0 );
}
OSG_PREFIX void OGDeleteSema( og_sema_t os )
{
CloseHandle( os );
}
OSG_PREFIX og_tls_t OGCreateTLS()
{
return (og_tls_t)(intptr_t)TlsAlloc();
}
OSG_PREFIX void OGDeleteTLS( og_tls_t key )
{
TlsFree( (DWORD)(intptr_t)key );
}
OSG_PREFIX void * OGGetTLS( og_tls_t key )
{
return TlsGetValue( (DWORD)(intptr_t)key );
}
OSG_PREFIX void OGSetTLS( og_tls_t key, void * data )
{
TlsSetValue( (DWORD)(intptr_t)key, data );
}
#elif defined( __wasm__ )
//We don't actually have any function defintions here.
//The outside system will handle it.
#else
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <sys/stat.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/time.h>
#include <semaphore.h>
#include <unistd.h>
OSG_PREFIX void OGSleep( int is )
{
sleep( is );
}
OSG_PREFIX void OGUSleep( int ius )
{
usleep( ius );
}
OSG_PREFIX double OGGetAbsoluteTime()
{
struct timeval tv;
gettimeofday( &tv, 0 );
return ((double)tv.tv_usec)/1000000. + (tv.tv_sec);
}
OSG_PREFIX double OGGetFileTime( const char * file )
{
struct stat buff;
int r = stat( file, &buff );
if( r < 0 )
{
return -1;
}
return buff.st_mtime;
}
OSG_PREFIX og_thread_t OGCreateThread( void * (routine)( void * ), void * parameter )
{
pthread_t * ret = (pthread_t *)malloc( sizeof( pthread_t ) );
if( !ret ) return 0;
int r = pthread_create( ret, 0, routine, parameter );
if( r )
{
free( ret );
return 0;
}
return (og_thread_t)ret;
}
OSG_PREFIX void * OGJoinThread( og_thread_t ot )
{
void * retval;
if( !ot )
{
return 0;
}
pthread_join( *(pthread_t*)ot, &retval );
OSG_TERM_THREAD_CODE
free( ot );
return retval;
}
OSG_PREFIX void OGCancelThread( og_thread_t ot )
{
if( !ot )
{
return;
}
#ifdef ANDROID
pthread_kill( *(pthread_t*)ot, SIGTERM );
#else
pthread_cancel( *(pthread_t*)ot );
#endif
OSG_TERM_THREAD_CODE
free( ot );
}
OSG_PREFIX og_mutex_t OGCreateMutex()
{
pthread_mutexattr_t mta;
og_mutex_t r = malloc( sizeof( pthread_mutex_t ) );
if( !r ) return 0;
pthread_mutexattr_init(&mta);
pthread_mutexattr_settype(&mta, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init( (pthread_mutex_t *)r, &mta );
return r;
}
OSG_PREFIX void OGLockMutex( og_mutex_t om )
{
if( !om )
{
return;
}
pthread_mutex_lock( (pthread_mutex_t*)om );
}
OSG_PREFIX void OGUnlockMutex( og_mutex_t om )
{
if( !om )
{
return;
}
pthread_mutex_unlock( (pthread_mutex_t*)om );
}
OSG_PREFIX void OGDeleteMutex( og_mutex_t om )
{
if( !om )
{
return;
}
pthread_mutex_destroy( (pthread_mutex_t*)om );
free( om );
}
OSG_PREFIX og_sema_t OGCreateSema()
{
sem_t * sem = (sem_t *)malloc( sizeof( sem_t ) );
if( !sem ) return 0;
sem_init( sem, 0, 0 );
return (og_sema_t)sem;
}
OSG_PREFIX int OGGetSema( og_sema_t os )
{
int valp;
sem_getvalue( (sem_t*)os, &valp );
return valp;
}
OSG_PREFIX void OGLockSema( og_sema_t os )
{
sem_wait( (sem_t*)os );
}
OSG_PREFIX void OGUnlockSema( og_sema_t os )
{
sem_post( (sem_t*)os );
}
OSG_PREFIX void OGDeleteSema( og_sema_t os )
{
sem_destroy( (sem_t*)os );
free(os);
}
OSG_PREFIX og_tls_t OGCreateTLS()
{
pthread_key_t ret = 0;
pthread_key_create(&ret, 0);
return (og_tls_t)(intptr_t)ret;
}
OSG_PREFIX void OGDeleteTLS( og_tls_t key )
{
pthread_key_delete( (pthread_key_t)(intptr_t)key );
}
OSG_PREFIX void * OGGetTLS( og_tls_t key )
{
return pthread_getspecific( (pthread_key_t)(intptr_t)key );
}
OSG_PREFIX void OGSetTLS( og_tls_t key, void * data )
{
pthread_setspecific( (pthread_key_t)(intptr_t)key, data );
}
#endif
#ifdef __cplusplus
};
#endif
#endif //OSG_NO_IMPLEMENTATION
#endif //_OS_GENERIC_H

View file

@ -0,0 +1,153 @@
//Copyright (c) 2011 <>< Charles Lohr - Under the MIT/x11 or NewBSD License you choose.
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "CNFG.h"
#include "os_generic.h"
unsigned frames = 0;
unsigned long iframeno = 0;
void HandleKey( int keycode, int bDown )
{
if( keycode == 65307 ) exit( 0 );
printf( "Key: %d -> %d\n", keycode, bDown );
}
void HandleButton( int x, int y, int button, int bDown )
{
printf( "Button: %d,%d (%d) -> %d\n", x, y, button, bDown );
}
void HandleMotion( int x, int y, int mask )
{
// printf( "Motion: %d,%d (%d)\n", x, y, mask );
}
int HandleDestroy()
{
printf( "Destroying\n" );
exit(10);
return 0;
}
short screenx, screeny;
int main()
{
int i, x, y;
double ThisTime;
double LastFPSTime = OGGetAbsoluteTime();
double LastFrameTime = OGGetAbsoluteTime();
double SecToWait;
int linesegs = 0;
CNFGBGColor = 0x800000;
//Deprecated CNFGDialogColor = 0x444444;
CNFGPrepareForTransparency();
CNFGSetupFullscreen( "Test Bench", 0 );
while(CNFGHandleInput())
{
int i, pos;
float f;
iframeno++;
RDPoint pto[3];
CNFGClearFrame();
CNFGColor( 0xFFFFFF );
CNFGGetDimensions( &screenx, &screeny );
// DrawFrame();
/*
pto[0].x = 100;
pto[0].y = 100;
pto[1].x = 200;
pto[1].y = 100;
pto[2].x = 100;
pto[2].y = 200;
CNFGTackPoly( &pto[0], 3 );
CNFGColor( 0xFF00FF );
*/
/* CNFGTackSegment( pto[0].x, pto[0].y, pto[1].x, pto[1].y );
CNFGTackSegment( pto[1].x, pto[1].y, pto[2].x, pto[2].y );
CNFGTackSegment( pto[2].x, pto[2].y, pto[0].x, pto[0].y );
*/
CNFGClearTransparencyLevel();
CNFGDrawToTransparencyMode(1);
CNFGTackRectangle( 0, 0, 260, 260 );
CNFGTackRectangle( 400, 400, 600, 600 );
CNFGPenX = 300;
CNFGPenY = 200;
CNFGSetLineWidth( 7 + sin(iframeno)*5 );
CNFGDrawText( "HELLO!", 5 );
CNFGDrawToTransparencyMode(0);
CNFGSetLineWidth( 1 );
CNFGDrawText( "HELLO!", 5 );
CNFGDrawBox( 0, 0, 260, 260 );
CNFGPenX = 10; CNFGPenY = 10;
pos = 0;
CNFGColor( 0xffffff );
for( i = 0; i < 1; i++ )
{
int c;
char tw[2] = { 0, 0 };
for( c = 0; c < 256; c++ )
{
tw[0] = c;
CNFGPenX = ( c % 16 ) * 16+5;
CNFGPenY = ( c / 16 ) * 16+5;
CNFGDrawText( tw, 2 );
}
}
CNFGPenX = 0;
CNFGPenY = 0;
RDPoint pp[3];
CNFGColor( 0x00FF00 );
pp[0].x = (short)(-200*sin((float)(iframeno)*.01) + 500);
pp[0].y = (short)(-200*cos((float)(iframeno)*.01) + 500);
pp[1].x = (short)(200*sin((float)(iframeno)*.01) + 500);
pp[1].y = (short)(00*cos((float)(iframeno)*.01) + 500);
pp[2].x = (short)(200*sin((float)(iframeno)*.01) + 500);
pp[2].y = (short)(200*cos((float)(iframeno)*.01) + 500);
CNFGTackPoly( pp, 3 );
frames++;
CNFGSwapBuffers();
ThisTime = OGGetAbsoluteTime();
if( ThisTime > LastFPSTime + 1 )
{
printf( "FPS: %d\n", frames );
frames = 0;
linesegs = 0;
LastFPSTime+=1;
}
SecToWait = .016 - ( ThisTime - LastFrameTime );
LastFrameTime += .016;
if( SecToWait > 0 )
OGUSleep( (int)( SecToWait * 1000000 ) );
}
return(0);
}

View file

@ -0,0 +1,293 @@
//Copyright (c) 2011 <>< Charles Lohr - Under the MIT/x11 or NewBSD License you choose.
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "os_generic.h"
#define CNFG3D
#define CNFG_IMPLEMENTATION
//#define CNFGOGL
//#define CNFGRASTERIZER
//#define CNFG_WINDOWS_DISABLE_BATCH
#include "CNFG.h"
unsigned frames = 0;
unsigned long iframeno = 0;
void HandleKey( int keycode, int bDown )
{
printf( "Key: %d -> %d\n", keycode, bDown );
printf( "Scancode: %d -> %d\n", CNFGLastScancode, bDown );
printf( "Char: %c\n", CNFGLastCharacter );
if( keycode == 27 || keycode == CNFG_KEY_ESCAPE ) exit( 0 );
}
void HandleButton( int x, int y, int button, int bDown )
{
printf( "Button: %d,%d (%d) -> %d\n", x, y, button, bDown );
}
void HandleMotion( int x, int y, int mask )
{
// printf( "Motion: %d,%d (%d)\n", x, y, mask );
}
int HandleDestroy()
{
printf( "Destroying\n" );
exit(10);
return 0;
}
#define HMX 40
#define HMY 40
short screenx, screeny;
float Heightmap[HMX*HMY];
void DrawHeightmap()
{
int x, y;
float fdt = ((iframeno++)%(360*10))/10.0;
float eye[3] = { (float)sin(fdt*(3.14159/180.0))*1, (float)cos(fdt*(3.14159/180.0))*1, 1 };
float at[3] = { 0,0, 0 };
float up[3] = { 0,0, 1 };
tdSetViewport( -1, -1, 1, 1, screenx, screeny );
tdMode( tdPROJECTION );
tdIdentity( gSMatrix );
tdPerspective( 40, ((float)screenx)/((float)screeny), .1, 200., gSMatrix );
tdMode( tdMODELVIEW );
tdIdentity( gSMatrix );
tdTranslate( gSMatrix, 0, 0, -40 );
tdLookAt( gSMatrix, eye, at, up );
for( x = 0; x < HMX-1; x++ )
for( y = 0; y < HMY-1; y++ )
{
float tx = x-HMX/2;
float ty = y-HMY/2;
float pta[3];
float ptb[3];
float ptc[3];
float ptd[3];
float normal[3];
float lightdir[3] = { 1, -1, 1 };
float tmp1[3];
float tmp2[3];
RDPoint pto[6];
pta[0] = tx+0; pta[1] = ty+0; pta[2] = Heightmap[(x+0)+(y+0)*HMX];
ptb[0] = tx+1; ptb[1] = ty+0; ptb[2] = Heightmap[(x+1)+(y+0)*HMX];
ptc[0] = tx+0; ptc[1] = ty+1; ptc[2] = Heightmap[(x+0)+(y+1)*HMX];
ptd[0] = tx+1; ptd[1] = ty+1; ptd[2] = Heightmap[(x+1)+(y+1)*HMX];
tdPSub( pta, ptb, tmp2 );
tdPSub( ptc, ptb, tmp1 );
tdCross( tmp1, tmp2, normal );
tdFinalPoint( pta, pta );
tdFinalPoint( ptb, ptb );
tdFinalPoint( ptc, ptc );
tdFinalPoint( ptd, ptd );
if( pta[2] >= 1.0 ) continue;
if( ptb[2] >= 1.0 ) continue;
if( ptc[2] >= 1.0 ) continue;
if( ptd[2] >= 1.0 ) continue;
if( pta[2] < 0 ) continue;
if( ptb[2] < 0 ) continue;
if( ptc[2] < 0 ) continue;
if( ptd[2] < 0 ) continue;
/* if( pta[3] < -1 ) continue;
if( ptb[3] < -1 ) continue;
if( ptc[3] < -1 ) continue;
if( ptd[3] < -1 ) continue;
*/
// if( pta[2] < 0 ) continue;
// if( ptb[2] < 0 ) continue;
// if( ptc[2] < 0 ) continue;
// if( ptd[2] < 0 ) continue;
pto[0].x = pta[0]; pto[0].y = pta[1];
pto[1].x = ptb[0]; pto[1].y = ptb[1];
pto[2].x = ptd[0]; pto[2].y = ptd[1];
pto[3].x = ptc[0]; pto[3].y = ptc[1];
pto[4].x = ptd[0]; pto[4].y = ptd[1];
pto[5].x = pta[0]; pto[5].y = pta[1];
// CNFGColor(((x+y)&1)?0xFFFFFFFF:0x000000FF);
float bright = tdDot( normal, lightdir );
if( bright < 0 ) bright = 0;
CNFGColor( 0xff | (((int)( bright * 50 ))<<24) );
// CNFGTackPoly( &pto[0], 3 ); CNFGTackPoly( &pto[3], 3 );
CNFGTackSegment( pta[0], pta[1], ptb[0], ptb[1] );
// CNFGTackSegment( ptb[0], ptb[1], ptc[0], ptc[1] );
// CNFGTackSegment( ptc[0], ptc[1], ptd[0], ptd[1] );
// CNFGTackSegment( ptd[0], ptd[1], pta[0], pta[1] );
CNFGTackSegment( pta[0], pta[1], ptc[0], ptc[1] );
// CNFGTackSegment( ptd[0], ptd[1], ptb[0], ptb[1] );
}
/*
for( f = 0; f <= 6.28; f+=0.01 )
{
tdPSet( pta, cos( f ), sin(f), cos( f * 10. + ThisTime) );
tdPSet( ptb, cos( f - 0.01 ), sin(f - 0.01), cos( (f-0.01) * 10. + ThisTime) );
// printf( "(%f, %f, %f) -> ", pta[0], pta[1], pta[2] );
tdFinalPoint( pta, pta );
tdFinalPoint( ptb, ptb );
// printf( "%f, %f, %f\n", pta[0], pta[1], pta[2] );
CNFGTackSegment( pta[0], pta[1], ptb[0], ptb[1] );
}
*/
}
uint32_t randomtexturedata[65536];
int main()
{
int i, x, y;
double ThisTime;
double LastFPSTime = OGGetAbsoluteTime();
double LastFrameTime = OGGetAbsoluteTime();
double SecToWait;
int linesegs = 0;
CNFGBGColor = 0x000080FF; //Darkblue
CNFGSetup( "Test Bench", 640, 480 );
// CNFGSetupFullscreen( "Test Bench", 0 );
//CNFGSetLineWidth( 2 );
for( x = 0; x < HMX; x++ )
for( y = 0; y < HMY; y++ )
{
Heightmap[x+y*HMX] = tdPerlin2D( x, y )*8.;
}
while(CNFGHandleInput())
{
int i, pos;
float f;
iframeno++;
RDPoint pto[3];
CNFGClearFrame();
CNFGColor( 0xFFFFFFFF );
CNFGGetDimensions( &screenx, &screeny );
// Mesh in background
DrawHeightmap();
/*
pto[0].x = 100;
pto[0].y = 100;
pto[1].x = 200;
pto[1].y = 100;
pto[2].x = 100;
pto[2].y = 200;
CNFGTackPoly( &pto[0], 3 );
CNFGColor( 0xFF00FF );
*/
/* CNFGTackSegment( pto[0].x, pto[0].y, pto[1].x, pto[1].y );
CNFGTackSegment( pto[1].x, pto[1].y, pto[2].x, pto[2].y );
CNFGTackSegment( pto[2].x, pto[2].y, pto[0].x, pto[0].y );
*/
CNFGColor( 0xffffffff );
CNFGDialogColor = 0x202020ff;
CNFGDrawBox( 300, 0, 320, 20 );
// Square behind text
CNFGColor( 0x444444FF );
CNFGTackRectangle( 0, 0, 260, 260 );
CNFGPenX = 10; CNFGPenY = 10;
// Text
pos = 0;
CNFGColor( 0xffffffff );
for( i = 0; i < 1; i++ )
{
int c;
char tw[2] = { 0, 0 };
for( c = 0; c < 256; c++ )
{
tw[0] = c;
CNFGPenX = ( c % 16 ) * 16+5;
CNFGPenY = ( c / 16 ) * 16+5;
CNFGDrawText( tw, 2 );
}
}
// Green triangles
CNFGPenX = 0;
CNFGPenY = 0;
for( i = 0; i < 400; i++ )
{
RDPoint pp[3];
CNFGColor( 0x00FF00FF ); //Green
pp[0].x = (short)(50*sin((float)(i+iframeno)*.01) + (i%20)*30);
pp[0].y = (short)(50*cos((float)(i+iframeno)*.01) + (i/20)*20);
pp[1].x = (short)(20*sin((float)(i+iframeno)*.01) + (i%20)*30);
pp[1].y = (short)(50*cos((float)(i+iframeno)*.01) + (i/20)*20);
pp[2].x = (short)(10*sin((float)(i+iframeno)*.01) + (i%20)*30);
pp[2].y = (short)(30*cos((float)(i+iframeno)*.01) + (i/20)*20);
CNFGTackPoly( pp, 3 );
}
int x, y;
for( y = 0; y < 256; y++ )
for( x = 0; x < 256; x++ )
randomtexturedata[x+y*256] = x | ((x*394543L+y*355+iframeno)<<8);
CNFGBlitImage( randomtexturedata, 100, 300, 256, 256 );
frames++;
CNFGSwapBuffers();
ThisTime = OGGetAbsoluteTime();
if( ThisTime > LastFPSTime + 1 )
{
printf( "FPS: %d\n", frames );
frames = 0;
linesegs = 0;
LastFPSTime+=1;
}
SecToWait = .016 - ( ThisTime - LastFrameTime );
LastFrameTime += .016;
if( SecToWait > 0 )
OGUSleep( (int)( SecToWait * 1000000 ) );
}
return(0);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,62 @@
//Make it so we don't need to include any other C files in our build.
#define CNFG_IMPLEMENTATION
//Optional: Use OpenGL to do rendering on appropriate systems.
#define CNFGOGL
#include "CNFG.h"
void HandleKey( int keycode, int bDown ) { }
void HandleButton( int x, int y, int button, int bDown ) { }
void HandleMotion( int x, int y, int mask ) { }
int HandleDestroy() { return 0; }
int main()
{
CNFGSetup( "Example App", 1024, 768 );
while( CNFGHandleInput() )
{
CNFGBGColor = 0x000080ff; //Dark Blue Background
short w, h;
CNFGClearFrame();
CNFGGetDimensions( &w, &h );
//Change color to white.
CNFGColor( 0xffffffff );
CNFGPenX = 1; CNFGPenY = 1;
CNFGDrawText( "Hello, World", 2 );
//Draw a white pixel at 3,0 30
CNFGTackPixel( 30, 30 );
//Draw a line from 50,50 to 100,50
CNFGTackSegment( 50, 50, 100, 50 );
//Dark Red Color Select
CNFGColor( 0x800000ff );
//Draw 50x50 box starting at 100,50
CNFGTackRectangle( 100, 50, 150, 100 );
//Draw a triangle
RDPoint points[3] = { { 30, 36 }, { 20, 50 }, { 40, 50 } };
CNFGTackPoly( points, 3 );
// Blit random pixel data
{
static uint32_t data[64*64];
int x, y;
for( y = 0; y < 64; y++ ) for( x = 0; x < 64; x++ )
data[x+y*64] = 0xff | (rand()<<8);
CNFGBlitImage( data, 120, 190, 64, 64 );
}
//Display the image and wait for time to display next frame.
CNFGSwapBuffers();
}
}

View file

@ -0,0 +1,3 @@
set TCC=C:\tcc\tcc
%TCC% rawdraw.c -o rawdraw.exe -luser32 -lgdi32 -lopengl32 C:/windows/system32/msvcrt.dll

View file

@ -0,0 +1,24 @@
#include <stdio.h>
int main( int argc, char ** argv )
{
if( argc != 2 )
{
fprintf( stderr, "Error: need a symbol name\n" );
return -1;
}
int c;
int count = 0;
printf( "unsigned char %s[] = {", argv[1] );
while( ( c = getchar() ) != EOF )
{
if( ( count & 0x1f ) == 0 )
{
printf( "\n\t" );
}
printf( "0x%02x, ", c );
count++;
}
printf( "};\n" );
}

View file

@ -0,0 +1,546 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>cnhttp</title>
<style>
body { background-color: #000080; }
document : {overflow: hidden; width=100%; height=100%;}
</style>
</head>
<body style="height:100%;margin:0; padding:0">
<div STYLE="positon:absolute;z-index:3">
<canvas width=500 height=500 id=canvas></canvas>
</div>
<div STYLE="position:absolute;bottom:0;width:640;z-index:10;color:white;a:red;height:50px"><input type=submit value="fs" onclick="ToggleFS();"><div id=status></div></div>
<script>
let rendering = false;
let fullscreen = false;
let fullscreenoverride = null;
function ToggleFS() { fullscreenoverride = !fullscreenoverride; }
//Configure WebGL Stuff (allow to be part of global context)
let canvas = document.getElementById('canvas');
let wgl = canvas.getContext('webgl');
if( !wgl )
{
//Janky - on Firefox 83, with NVIDIA GPU, you need to ask twice.
wgl = canvas.getContext('webgl');
}
let wglShader = null; //Standard flat color shader
let wglABV = null; //Array buffer for vertices
let wglABC = null; //Array buffer for colors.
let wglUXFRM = null; //Uniform location for transform on solid colors
//Utility stuff for WebGL sahder creation.
function wgl_makeShader( vertText, fragText )
{
let vert = wgl.createShader(wgl.VERTEX_SHADER);
wgl.shaderSource(vert, vertText );
wgl.compileShader(vert);
if (!wgl.getShaderParameter(vert, wgl.COMPILE_STATUS)) {
alert(wgl.getShaderInfoLog(vert));
}
let frag = wgl.createShader(wgl.FRAGMENT_SHADER);
wgl.shaderSource(frag, fragText );
wgl.compileShader(frag);
if (!wgl.getShaderParameter(frag, wgl.COMPILE_STATUS)) {
alert(wgl.getShaderInfoLog(frag));
}
let ret = wgl.createProgram();
wgl.attachShader(ret, frag);
wgl.attachShader(ret, vert);
wgl.linkProgram(ret);
wgl.bindAttribLocation( ret, 0, "a0" );
wgl.bindAttribLocation( ret, 1, "a1" );
return ret;
}
{
//We load two shaders, one is a solid-color shader, for most rawdraw objects.
wglShader = wgl_makeShader(
"uniform vec4 xfrm; attribute vec3 a0; attribute vec4 a1; varying vec4 vc; void main() { gl_Position = vec4( a0.xy*xfrm.xy+xfrm.zw, a0.z, 0.5 ); vc = a1; }",
"precision mediump float; varying vec4 vc; void main() { gl_FragColor = vec4(vc.xyzw); }" );
wglUXFRM = wgl.getUniformLocation(wglShader, "xfrm" );
//Compile the shaders.
wgl.useProgram(wglShader);
//Get some vertex/color buffers, to put geometry in.
wglABV = wgl.createBuffer();
wglABC = wgl.createBuffer();
//We're using two buffers, so just enable them, now.
wgl.enableVertexAttribArray(0);
wgl.enableVertexAttribArray(1);
//Enable alpha blending
wgl.enable(wgl.BLEND);
wgl.blendFunc(wgl.SRC_ALPHA, wgl.ONE_MINUS_SRC_ALPHA);
console.log( wgl );
}//Do webgl work that must happen every frame.
//Buffered geometry system.
//This handles buffering a bunch of lines/segments, and using them all at once.
globalv = null;
function CNFGEmitBackendTrianglesJS( vertsF, colorsI, vertcount )
{
if( vertcount <= 0 ) return;
const ab = wgl.ARRAY_BUFFER;
wgl.bindBuffer(ab, wglABV);
wgl.bufferData(ab, vertsF, wgl.DYNAMIC_DRAW);
wgl.vertexAttribPointer(0, 3, wgl.FLOAT, false, 0, 0);
wgl.bindBuffer(ab, wglABC);
wgl.bufferData(ab, new Uint8Array( colorsI.buffer ), wgl.DYNAMIC_DRAW);
wgl.vertexAttribPointer(1, 4, wgl.UNSIGNED_BYTE, true, 0, 0);
wgl.drawArrays(wgl.TRIANGLES, 0, vertcount );
globalv = vertsF;
}
//Blitting functionality.
let wglBlit = null; //Blitting shader for texture
let wglTex = null; //Texture handle for blitting.
let wglUXFRMBlit = null; //Uniform location for transform on blitter
//We are not currently supporting the software renderer.
//We load two shaders, the other is a texture shader, for blitting things.
wglBlit = wgl_makeShader(
"uniform vec4 xfrm; attribute vec3 a0; attribute vec4 a1; varying vec2 tc; void main() { gl_Position = vec4( a0.xy*xfrm.xy+xfrm.zw, a0.z, 0.5 ); tc = a1.xy; }",
"precision mediump float; varying vec2 tc; uniform sampler2D tex; void main() { gl_FragColor = texture2D(tex,tc).wzyx;}" );
wglUXFRMBlit = wgl.getUniformLocation(wglBlit, "xfrm" );
function CNFGBlitImageInternal(uint8memdata, x, y, w, h )
{
if( w <= 0 || h <= 0 ) return;
wgl.useProgram(wglBlit);
//Most of the time we don't use textures, so don't initiate at start.
if( wglTex == null ) wglTex = wgl.createTexture();
wgl.activeTexture(wgl.TEXTURE0);
const t2d = wgl.TEXTURE_2D;
wgl.bindTexture(t2d, wglTex);
//Note that unlike the normal color operation, we don't have an extra offset.
wgl.uniform4f( wglUXFRMBlit,
1./wgl.viewportWidth, -1./wgl.viewportHeight,
-.5+x/wgl.viewportWidth, .5-y/wgl.viewportHeight );
//These parameters are required. Not sure why the defaults don't work.
wgl.texParameteri(t2d, wgl.TEXTURE_WRAP_T, wgl.CLAMP_TO_EDGE);
wgl.texParameteri(t2d, wgl.TEXTURE_WRAP_S, wgl.CLAMP_TO_EDGE);
wgl.texParameteri(t2d, wgl.TEXTURE_MIN_FILTER, wgl.NEAREST);
wgl.texImage2D(t2d, 0, wgl.RGBA, w, h, 0, wgl.RGBA,
wgl.UNSIGNED_BYTE, uint8memdata );
CNFGEmitBackendTrianglesJS(
new Float32Array( [0,0,0, w,0,0, w,h,0, 0,0,0, w,h,0, 0,h,0 ] ),
new Uint8Array( [0,0,0,0, 255,0,0,0, 255,255,0,0, 0,0,0,0, 255,255,0,0, 0,255,0,0] ),
6 );
wgl.useProgram(wglShader);
};
//Set when swap buffers is called by host app.
let frame_transfer_done = false;
// rest of functionality
function CNFGClearFrameInternal( color )
{
color |= 0;
wgl.clearColor( ((color>>24)&0xff)/255., ((color>>16)&0xff)/255., ((color>>8)&0xff)/255., ((color>>0)&0xff)/255. );
wgl.clear( wgl.COLOR_BUFFER_BIT | wgl.COLOR_DEPTH_BIT );
}
function CNFGGetDimensions(pw, ph)
{
HEAP16[pw>>1] = canvas.width;
HEAP16[ph>>1] = canvas.height;
}
function OGGetAbsoluteTime() { return new Date().getTime()/1000.; }
globalwebsocket = null;
globalwebsocket_time = -10000;
globalwebsocket_connected = false;
//Preallocate a large accumulated buffer.
accumulated_buffer = new Uint32Array( 4000000 );
accumulated_buffer_place = 0;
request_h = 384;
request_w = 512;
//Other global state
cnfg_color = 0xffffffff;
function SendSwap()
{
if( !globalwebsocket_connected ) return;
let w = wgl.viewportWidth;
let h = wgl.viewportHeight;
if( w === undefined )
{
w = wgl.drawingBufferWidth;
h = wgl.drawingBufferHeight;
}
frame_transfer_done = false;
globalwebsocket.send( new Uint8Array( [ 83, 87, 65, 80, w, w>>8, h, h>>8 ] ) ); //"SWAP"
}
function HandleMotion( x, y, but )
{
if( globalwebsocket_connected )
globalwebsocket.send( new Uint8Array( [ 77, 79, 84, 78, x, x>>8, y, y>>8, 0, 0, 0, but ] ) ); //"MOTN"
}
function HandleButton( x, y, btn, down )
{
if( globalwebsocket_connected )
globalwebsocket.send( new Uint8Array( [ 66, 85, 84, 78, x, x>>8, y, y>>8, 0, 0, down, btn ] ) ); //"BUTN"
}
function HandleKey( key, down )
{
if( globalwebsocket_connected )
globalwebsocket.send( new Uint8Array( [ 75, 69, 89, 66, 0, 0, key, down ] ) ); //"KEYB"
}
///////////////////////////////////////////////////////////////////////////////////////////
// Rawdraw functions
wgl_last_width_over_2 = .5;
CNFG_BATCH = 262144;
CNFGVertDataV = new Float32Array(CNFG_BATCH*3);
CNFGVertDataC = new Uint32Array(CNFG_BATCH);
CNFGVertPlace = 0;
function CNFGFlushRender()
{
CNFGEmitBackendTrianglesJS( CNFGVertDataV, CNFGVertDataC, CNFGVertPlace );
CNFGVertPlace = 0;
}
function EmitQuad( cx0, cy0, cx1, cy1, cx2, cy2, cx3, cy3 )
{
//Because quads are really useful, but it's best to keep them all triangles if possible.
//This lets us draw arbitrary quads.
if( CNFGVertPlace >= CNFG_BATCH-6 ) CNFGFlushRender();
CNFGVertDataV[CNFGVertPlace*3+0] = cx0; CNFGVertDataV[CNFGVertPlace*3+1] = cy0;
CNFGVertDataV[CNFGVertPlace*3+3] = cx1; CNFGVertDataV[CNFGVertPlace*3+4] = cy1;
CNFGVertDataV[CNFGVertPlace*3+6] = cx2; CNFGVertDataV[CNFGVertPlace*3+7] = cy2;
CNFGVertDataV[CNFGVertPlace*3+9] = cx2; CNFGVertDataV[CNFGVertPlace*3+10] = cy2;
CNFGVertDataV[CNFGVertPlace*3+12] = cx1; CNFGVertDataV[CNFGVertPlace*3+13] = cy1;
CNFGVertDataV[CNFGVertPlace*3+15] = cx3; CNFGVertDataV[CNFGVertPlace*3+16] = cy3;
CNFGVertDataC[CNFGVertPlace+0] = cnfg_color;
CNFGVertDataC[CNFGVertPlace+1] = cnfg_color;
CNFGVertDataC[CNFGVertPlace+2] = cnfg_color;
CNFGVertDataC[CNFGVertPlace+3] = cnfg_color;
CNFGVertDataC[CNFGVertPlace+4] = cnfg_color;
CNFGVertDataC[CNFGVertPlace+5] = cnfg_color;
CNFGVertPlace += 6;
}
function CNFGTackPixel( x1, y1 )
{
x1++; y1++;
let l2 = wgl_last_width_over_2;
let l2u = wgl_last_width_over_2+0.5;
EmitQuad( x1-l2u, y1-l2u, x1+l2, y1-l2u, x1-l2u, y1+l2, x1+l2, y1+l2 );
}
function CNFGTackSegment( x1, y1, x2, y2 )
{
// console.log( x1 + " " + y1 + " " + x2 + " " + y2 );
let ix1 = x1;
let iy1 = y1;
let ix2 = x2;
let iy2 = y2;
let dx = ix2-ix1;
let dy = iy2-iy1;
let imag = 1./Math.sqrt(dx*dx+dy*dy);
dx *= imag;
dy *= imag;
let orthox = dy*wgl_last_width_over_2;
let orthoy =-dx*wgl_last_width_over_2;
ix2 += dx/2 + 0.5;
iy2 += dy/2 + 0.5;
ix1 -= dx/2 - 0.5;
iy1 -= dy/2 - 0.5;
//This logic is incorrect. XXX FIXME.
EmitQuad( (ix1 - orthox), (iy1 - orthoy), (ix1 + orthox), (iy1 + orthoy), (ix2 - orthox), (iy2 - orthoy), ( ix2 + orthox), ( iy2 + orthoy) );
}
function CNFGTackRectangle( x1, y1, x2, y2 )
{
let ix1 = x1;
let iy1 = y1;
let ix2 = x2;
let iy2 = y2;
EmitQuad( ix1,iy1,ix2,iy1,ix1,iy2,ix2,iy2 );
}
function CNFGTackPoly( points, verts )
{
verts |= 0;
let i = 0|0;
let tris = verts-2;
if( CNFGVertPlace >= CNFG_BATCH-tris*3 ) CNFGFlushRender();
for( i = 0; i < tris; i++ )
{
CNFGVertDataV[CNFGVertPlace*3+0] = points[0];
CNFGVertDataV[CNFGVertPlace*3+1] = points[1];
CNFGVertDataV[CNFGVertPlace*3+3] = points[i*2+2];
CNFGVertDataV[CNFGVertPlace*3+4] = points[i*2+3];
CNFGVertDataV[CNFGVertPlace*3+6] = points[i*2+4];
CNFGVertDataV[CNFGVertPlace*3+7] = points[i*2+5];
CNFGVertDataC[CNFGVertPlace+0] = cnfg_color;
CNFGVertDataC[CNFGVertPlace+1] = cnfg_color;
CNFGVertDataC[CNFGVertPlace+2] = cnfg_color;
CNFGVertPlace += 3;
}
}
function CNFGSetLineWidth( width )
{
wgl_last_width_over_2 = width/2.0;// + 0.5;
}
function TickWebsocket()
{
if( OGGetAbsoluteTime() - globalwebsocket_time > 3 )
{
document.getElementById( "status" ).innerHTML = "Connecting";
// Need to connect.
globalwebsocket_time = OGGetAbsoluteTime();
globalwebsocket_connected = false;
globalwebsocket = new WebSocket( "ws://" + location.host + "/d/ws/cmdbuf" );
globalwebsocket.binaryType = 'arraybuffer';
globalwebsocket.onopen = function()
{
document.getElementById( "status" ).innerHTML = "Connected";
globalwebsocket_connected = true;
SendSwap();
}
globalwebsocket.onmessage = function (event) {
let ab = event.data;
globalwebsocket_time = OGGetAbsoluteTime();
if( ab.byteLength == 0 )
{
//Process buffer.
let i = 0|0;
let lastdata = 0;
while( i < accumulated_buffer_place )
{
let data = accumulated_buffer[i++];
if( ( data >>> 24 ) & 0xf && ( ( data >>> 28) != 2 ) )
{
console.log( "Fault at " + i + " Reading " + data + " last " + lastdata + " // " + accumulated_buffer[i-1] + " " + accumulated_buffer[i-2] + " " + accumulated_buffer[i - 3] + " " + accumulated_buffer[i-4] );
break;
}
lastdata = data;
switch( data >>> 28 )
{
case 6:
case 0: // Continuation - we should never get here.
break;
case 1: // Color
{
let col = accumulated_buffer[i++];
cnfg_color = ( ( col >>> 24 ) & 0xff ) | ( ( ( col >>> 16 ) & 0xff ) << 8 ) | ( ( ( col >>> 8 ) & 0xff ) << 16 ) | ( ( ( col >>> 0 ) & 0xff ) << 24 );
break;
}
case 2:
CNFGTackPixel( data & 0x3fff, ( data >>> 14 ) & 0x3fff );
break;
case 3:
{
let nextfew = new Int16Array( accumulated_buffer.buffer.slice( i*4, i*4+8 ) ); i+=2;
CNFGTackSegment( nextfew[0], nextfew[1], nextfew[2], nextfew[3] );
break;
}
case 4:
{
let nextfew = new Int16Array( accumulated_buffer.buffer.slice( i*4, i*4+8 ) ); i+=2;
CNFGTackRectangle( nextfew[0], nextfew[1], nextfew[2], nextfew[3] );
break;
}
case 5:
{
let numpts = data & 0xfffff;
let ab = new Int16Array( accumulated_buffer.buffer.slice( i*4, i*4+numpts*4 ) );
i += numpts;
CNFGTackPoly( ab, numpts );
break;
}
case 7:
{
//break;
//blit!
CNFGFlushRender();
let xy = accumulated_buffer[i++];
let x = xy & 0xffff;
let y = (xy >>> 16 ) & 0xffff;
let w = data & 0x3fff;
let h = (data>>>14) & 0x3fff;
let ab = new Uint8Array( accumulated_buffer.buffer.slice( i*4, i*4+ w*h*4 ) );
i += w*h;
CNFGBlitImageInternal( ab, x, y, w, h )
break;
}
case 8:
{
CNFGFlushRender();
let numblocks = data & 0xff;
let fsreq = ( data >>> 8 ) & 0x1;
let titlelen = (data >>> 16 ) & 0xff;
cnfg_bgcolor = accumulated_buffer[i++];
let reqs = accumulated_buffer[i++];
request_w = reqs & 0xffff;
request_h = ( reqs >> 16 ) & 0xffff;
CNFGClearFrameInternal( cnfg_bgcolor );
let j = 0|0;
let title = new Uint8Array( accumulated_buffer.buffer.slice( i*4, i*4+titlelen ) );
i += numblocks - 3;
document.title = new TextDecoder().decode( title );
fullscreen = fsreq;
break;
}
case 9:
//Swap buffers (not realy used - we detect end of frame with a 0-length message.)
CNFGFlushRender();
break;
case 10: //set line width
CNFGSetLineWidth( data & 0xfff );
break;
}
}
frame_transfer_done = true;
accumulated_buffer_place = 0;
}
else
{
accumulated_buffer.set( new Uint32Array( ab ), accumulated_buffer_place );
accumulated_buffer_place += ab.byteLength/4;
}
}
globalwebsocket.onclose = function(event)
{
console.log( "Forced close." );
globalwebsocket_connected = false;
}
}
}
function FrameStart()
{
//console.log( globalwebsocket_connected + " " + frame_transfer_done );
if( !globalwebsocket_connected )
return;
if( fullscreenoverride === null )
{
// Don't affect fullscreen
}
else
{
fullscreen = fullscreenoverride;
}
//Fixup canvas sizes
if( fullscreen )
{
wgl.viewportWidth = canvas.width = window.innerWidth;
wgl.viewportHeight = canvas.height = window.innerHeight;
canvas.style = "position:absolute; top:0; left:0;"
}
else
{
wgl.viewportWidth = canvas.width = request_w;
wgl.viewportHeight = canvas.height = request_h;
}
if( frame_transfer_done )
{
//Make sure viewport and input to shader is correct.
//We do this so we can pass literal coordinates into the shader.
wgl.viewport( 0, 0, wgl.viewportWidth, wgl.viewportHeight );
//Update geometry transform (Scale/shift)
wgl.uniform4f( wglUXFRM,
1./wgl.viewportWidth, -1./wgl.viewportHeight,
-0.5, 0.5);
SendSwap();
}
}
function AnimationFrame()
{
TickWebsocket();
FrameStart();
requestAnimationFrame( AnimationFrame );
}
requestAnimationFrame( AnimationFrame );
canvas.addEventListener('mousemove', e => { HandleMotion( e.offsetX, e.offsetY, e.buttons ); } );
canvas.addEventListener('touchmove', e => { HandleMotion( e.touches[0].clientX, e.touches[0].clientY, 1 ); } );
canvas.addEventListener('mouseup', e => { HandleButton( e.offsetX, e.offsetY, e.button, 0 ); return false; } );
canvas.addEventListener('mousedown', e => { HandleButton( e.offsetX, e.offsetY, e.button, 1 ); return false; } );
document.addEventListener('keydown', e => { HandleKey( e.keyCode, 1 ); } );
document.addEventListener('keyup', e => { HandleKey( e.keyCode, 0 ); } );
/*
function SystemStart( title, w, h )
{
document.title = toUTF8( title );
wgl.viewportWidth = canvas.width = w;
wgl.viewportHeight = canvas.height = h;
}
*/
</script>
</body>
</html>

View file

@ -0,0 +1,86 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char * hitfiles;
char * OSGLineFromFile( FILE * f )
{
int c;
int mlen = 128;
int len = 0;
if( !f || ferror(f) || feof( f ) ) return 0;
char * ret = malloc( mlen );
while( 1 )
{
c = fgetc( f );
if( c == EOF || c == 0 || c == '\n' ) break;
if( c == '\r' ) continue;
ret[len++] = c;
if( len >= mlen-1 )
{
mlen += 128;
ret = realloc( ret, mlen );
}
}
ret[len] = 0;
return ret;
}
void IncludeFile( char * filename, char ** includes, int nrinc )
{
FILE * f = fopen( filename, "rb" );
if( !f )
{
fprintf( stderr, "Error: Could not open %s\n", filename );
exit( -5 );
}
while( !feof( f ) )
{
char * line = OSGLineFromFile( f );
if( line )
{
int match = 0;
if( strncmp( line, "#include \"", 10 ) == 0 )
{
int i;
for( i = 0; i < nrinc; i++ )
{
if( strncmp( line + 10, includes[i], strlen( includes[i] ) ) == 0 )
{
if( !hitfiles[i] )
{
IncludeFile( includes[i], includes, nrinc );
hitfiles[i] = 0;
}
match = 1;
}
}
}
if( !match )
{
puts( line );
}
}
}
}
int main( int argc, char ** argv )
{
if( argc < 3 )
{
fprintf( stderr, "Error: Usage: single_file_creator {file to turn into a single file} {list of files to include}\n" );
return -5;
}
hitfiles = malloc( argc );
memset( hitfiles, 0, sizeof(hitfiles) );
hitfiles[0] = 1;
IncludeFile( argv[1], argv + 1, argc - 1 );
return 0;
}

View file

@ -0,0 +1,2 @@
index.html
node_modules

View file

@ -0,0 +1,46 @@
all : subst index.html
#For tools (Works in Ubuntu 20.04 (Including WSL), Mint)
# sudo apt-get install clang-10 lld-10 binaryen
# sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-10 20
# sudo update-alternatives --install /usr/bin/wasm-ld wasm-ld /usr/bin/wasm-ld-10 20
#node-uglify lld clang-10 lld-10 binaryen
#Path to rawdraw
CFLAGS:=-I..
CLANG?=clang
WASMOPT?=wasm-opt
TERSER?=terser
TERSERFLAGS?= --ecma 2017 -d RAWDRAW_USE_LOOP_FUNCTION=false -d RAWDRAW_NEED_BLITTER=true
CFLAGS+=-DWASM -nostdlib --target=wasm32 \
-flto -Oz \
-Wl,--lto-O3 \
-Wl,--no-entry \
-Wl,--allow-undefined \
-Wl,--import-memory
WOFLAGS:=--asyncify --pass-arg=asyncify-imports@bynsyncify.* --pass-arg=asyncify-ignore-indirect
opt.js : template.js main.wasm
cat main.wasm | base64 | sed -e "$$ ! {/./s/$$/ \\\\/}" > blob_b64;
./subst template.js -s -f BLOB blob_b64 -o mid.js
#Comment the below line out if you don't want to uglify the javascript.
$(TERSER) $(TERSERFLAGS) mid.js -o opt.js
rm mid.js blob_b64
index.html : template.ht opt.js
./subst template.ht -s -f JAVASCRIPT_DATA opt.js -o $@
subst : subst.c
cc -o $@ $^
main.wasm: rawdraw.c
$(CLANG) $(CFLAGS) $^ -o $@
$(WASMOPT) $(WOFLAGS) -Oz main.wasm -o main.wasm
#wasm-objdump -d main.wasm > main.disassembly.txt
clean:
rm -rf main.wasm opt.js index.html blob_b64

View file

@ -0,0 +1,93 @@
# rawdrawwasm
rawdrawwasm
## Prerequisites
```
sudo apt-get install npm binaryen clang llvm lld
sudo npm install terser -g
```
You may need to add llvm bins to your path, i.e.
```
export PATH=$PATH:/usr/lib/llvm-10/bin
```
Note also: clang. llvm, lld-12 has also been tested and works.
Depending on your llvm version.
## Old Notes
My stab at rawdraw on wasm. This is based on:
https://github.com/zNoctum/wasm-tool
and has inspiration from:
https://github.com/cnlohr/wasm_integrated
Lots of stuff todo, hopefully this will get rolled into main rawdraw.
TODO notes for now:
* hook up button presses, and mouse input and destroy
* hook up sleep in a more meaningful way maybe?
* Make printf work.
* Make libc work.
* Figure out how to #include math.h
* Come up with guide on how to use this.
Check it out, live: https://cnlohr.github.io/rawdrawwasm/
I wanted to compare the difference beteen an asynchronous and synchronous style. Also, measure the overhead introudced by adding `--asyncify`. This is using the canvas 2d methods. Those are being abandoned.
```
//TEST ON CHROME:
//NOT RASTERIZER
// Async drive: ~7.2ms
// Sync drive: ~6.9ms
//sync w/o asyncify: 7.0ms.
//
//RASTERIZER
//Async: 7.5ms
//Sync: 3.9ms
//Sync w/o asyncify: 3.6ms
//TEST ON FIREFOX:
//Async: 17.3ms
//sync: 13.7ms
//Sync w/o asyncify: 13.3ms
//
//FF without including swap.
//Async: 11.5
//Sync: 11.5ms
//Sync w/o asyncify: 11.2ms
```
This was something I found convenient for a while...
```
function wasmloaded(wa)
{
instance = wa;
wasmExports = instance.exports;
instance.exports.main();
}
//If at all possible, we should attempt to load in-thread
//This will make the load not flash.
if( blob.length < 4096 )
{
let mod = new WebAssembly.Module(array);
var wa = new WebAssembly.Instance( mod, imports );
wasmloaded( wa );
}
else
{
WebAssembly.instantiate(array, imports).then( function(wa) { wasmloaded(wa.instance); } );
}
```

View file

@ -0,0 +1,83 @@
# Install LLVM to C:\Program Files\LLVM or set parameter below (Recommends LLVM-11)
# Install Binaryen to C:\binaryen\ or set parameter below (Recommends binaryen-98)
# If you want uglify-js, install node to your system paths, https://nodejs.org/en/download/
# Execute: npm install -g terser
# You can also install clang via msys2 with "pacman -S llvm mingw-w64-clang-x86_64-wasm-pack mingw-w64-clang-x86_64-binaryen lld"
#$Clang = "C:\Program Files\LLVM\bin\clang"
$Clang = "D:\msys64\usr\bin\clang"
# Assuming you have downloaded binaryen-98 and put it someheere that is in your paths.
#$WASMOpt = "wasm-opt"
$WASMOpt = "D:\msys64\clang64\bin\wasm-opt"
$OutFile = 'index.html'
$TemplateHT = 'template.ht'
$TemplateJS = 'template.js'
$MainWASM = 'main.wasm'
$InputC = 'rawdraw.c'
$IntermediateJS = 'main.js.tmp'
Write-Host "Using Clang at $Clang"
Write-Host "Compiling $InputC..."
$ClangProc = Start-Process -NoNewWindow -FilePath $Clang -PassThru -ArgumentList @(
'-I..',
'-DWASM',
'-nostdlib',
'--target=wasm32',
'-flto',
'-Oz',
'-Wl,--lto-O3',
'-Wl,--no-entry',
'-Wl,--allow-undefined',
'-Wl,--import-memory',
"-o $MainWASM",
$InputC
)
$ClangProc.WaitForExit();
Write-Host "Using Binaryen at $WASMOpt"
Write-Host 'Asyncifying...'
$AsyncProc = Start-Process -NoNewWindow -FilePath $WASMOpt -PassThru -ArgumentList @(
'--asyncify',
'--pass-arg=asyncify-imports@bynsyncify.*',
'--pass-arg=asyncify-ignore-indirect'
'-Oz',
$MainWASM,
"-o $MainWASM.opt"
)
$AsyncProc.WaitForExit();
Write-Host 'Merging files...'
Write-Host ("WASM Binary size: {0:N0} B" -f (Get-Item $PSScriptRoot\$MainWASM).length)
$WASMasB64 = [Convert]::ToBase64String([IO.File]::ReadAllBytes("$PSScriptRoot\$MainWASM.opt"))
#Remove-Item $MainWASM
$ContentHT = Get-Content $TemplateHT -Raw
$ContentJS = Get-Content $TemplateJS -Raw
$Output = $ContentJS.Replace('${BLOB}', $WASMasB64);
if (Get-Command "terser" -ErrorAction SilentlyContinue)
{
Set-Content $IntermediateJS -Value $Output
Write-Host ("Before minifaction: {0:N0} B" -f ($Output).length)
$Output = terser -ecma 2017 `
-d RAWDRAW_USE_LOOP_FUNCTION=false `
-d RAWDRAW_NEED_BLITTER=true `
$IntermediateJS
Write-Host ("After minifacation: {0:N0} B" -f ($Output).length)
Remove-Item $IntermediateJS
}
else
{
Write-Host 'terser not found. Not uglifying javascript.';
}
$Output = $ContentHT.Replace('${JAVASCRIPT_DATA}', $Output);
Set-Content $OutFile -Value $Output
Write-Host ("$OutFile size: {0:N0} B" -f (Get-Item $OutFile).length)

View file

@ -0,0 +1,276 @@
//Copyright (c) 2011 <>< Charles Lohr - Under the MIT/x11 or NewBSD License you choose.
#include "os_generic.h"
#define CNFG3D
#define CNFG_IMPLEMENTATION
//#define CNFGRASTERIZER
#include "CNFG.h"
double OGGetAbsoluteTime();
void OGUSleep( int us );
void prints( const char * sdebug );
void print( double idebug );
double sin( double x );
double cos( double x );
unsigned long iframeno = 0;
int lastmousex;
int lastmousey = 100;
void __attribute__((export_name("HandleKey"))) HandleKey( int keycode, int bDown )
{
print( keycode );
print( bDown );
}
void __attribute__((export_name("HandleButton"))) HandleButton( int x, int y, int button, int bDown )
{
print( x );
print( y );
print( button );
print( bDown );
}
void __attribute__((export_name("HandleMotion"))) HandleMotion( int x, int y, int mask )
{
lastmousex = x;
lastmousey = y;
}
void HandleDestroy()
{
//printf( "Destroying\n" );
}
#define HMX 40
#define HMY 40
short screenx, screeny;
float Heightmap[HMX*HMY];
void DrawHeightmap();
unsigned fpsframes = 0;
double fpstime = 0;
int __attribute__((export_name("main"))) main()
{
int i, x, y;
fpstime = OGGetAbsoluteTime();
//Setup colors.
CNFGBGColor = 0xff800000;
//Actually sets outside window title, and
//If not in full-screen mode, will also resize.
//CNFGSetup( "Test Bench", 640, 480 );
CNFGSetupFullscreen( "Test Bench", 0 );
//Configure heightmap.
for( x = 0; x < HMX; x++ )
for( y = 0; y < HMY; y++ )
{
Heightmap[x+y*HMX] = tdPerlin2D( x, y )*8.;
}
prints( "Main started. This will appear in your browser console." );
#ifdef RAWDRAW_USE_LOOP_FUNCTION
return 0;
}
int __attribute__((export_name("loop"))) loop()
{
#else
while(CNFGHandleInput())
#endif
{
int i, pos;
float f;
iframeno++;
RDPoint pto[3];
//Normally this would invoke callbacks, but
//at least currently, we do that out-of-band.
//CNFGHandleInput();
CNFGClearFrame();
CNFGColor( 0xFFFFFFFF );
CNFGGetDimensions( &screenx, &screeny );
CNFGPenX = 100;
CNFGPenY = 100;
//quick minitest
// CNFGDrawText("+hello!!",4 );
// CNFGSwapBuffers();
// continue;
// Mesh in background
DrawHeightmap();
// Square behind text
CNFGColor( 0xff444444 );
CNFGSetLineWidth(3);
CNFGTackSegment( 0, 50, 100, 50 );
CNFGTackSegment( 0, 50, 0, 150 );
CNFGTackRectangle( 2, 2+50, 345, 345+50 );
CNFGSetLineWidth(3);
// Text stuff in upper left.
pos = 0;
CNFGColor( 0xffffffff );
for( i = 0; i < 1; i++ )
{
int c;
char tw[2] = { 0, 0 };
for( c = 0; c < 256; c++ )
{
tw[0] = c;
CNFGPenX = ( c % 16 ) * 20+5;
CNFGPenY = ( c / 16 ) * 20+55;
CNFGDrawText( tw, 4 );
}
}
#if 1
// Green triangles
CNFGPenX = 0;
CNFGPenY = 0;
RDPoint pp[3];
for( i = 0; i < 400; i++ )
{
CNFGColor( 0xFF00FF00 );
pp[0].x = (short)(50*sin((float)(i+iframeno)*.01) + (i%20)*30);
pp[0].y = (short)(50*cos((float)(i+iframeno)*.01) + (i/20)*20)+100;
pp[1].x = (short)(20*sin((float)(i+iframeno)*.01) + (i%20)*30);
pp[1].y = (short)(50*cos((float)(i+iframeno)*.01) + (i/20)*20)+100;
pp[2].x = (short)(10*sin((float)(i+iframeno)*.01) + (i%20)*30);
pp[2].y = (short)(30*cos((float)(i+iframeno)*.01) + (i/20)*20)+100;
CNFGTackPoly( pp, 3 );
}
#endif
#if 0 //Profiling
extern int Add1( int i );
double now = OGGetAbsoluteTime();
double k = 4;
for( i = 0; i < 1000000; i++ )
{
//CNFGTackPoly( pp, 3 ); //3200 ns
CNFGTackPixel( 0,0 ); //340ns
//CNFGTackSegment( 0, 0, 10, 10 ); //157ns
//k = sin(k); //28ns
//k = Add1(k); //16ns
//NOTE: actual callback time is ~14ns, so loop overhead is about 10ns.
}
print( (OGGetAbsoluteTime() - now)*1000 );
print( k );
#endif
#if 1 //Enable alpha objec test
static uint32_t randomtexturedata[65536];
int x, y;
for( y = 0; y < 256; y++ )
for( x = 0; x < 256; x++ )
randomtexturedata[x+y*256] = x | ((x*394543L+y*355+iframeno)<<8);
CNFGBlitImage( randomtexturedata, 100, 300, 256, 256 );
#endif
fpsframes++;
CNFGSwapBuffers();
double Now = OGGetAbsoluteTime();
if( Now - fpstime >= 1 )
{
print( fpsframes );
fpsframes = 0;
fpstime += 1;
}
}
return(0);
}
void DrawHeightmap()
{
int x, y;
float fdt = ((iframeno++)%(360*10))/100.0 + lastmousex;
float eye[3] = { (float)sin(fdt*(3.14159/180.0))*1, (float)cos(fdt*(3.14159/180.0))*1, lastmousey*.01 };
float at[3] = { 0,0, 0 };
float up[3] = { 0,0, 1 };
tdSetViewport( -1, -1, 1, 1, screenx, screeny );
tdMode( tdPROJECTION );
tdIdentity( gSMatrix );
tdPerspective( 40, ((float)screenx)/((float)screeny), .1, 200., gSMatrix );
tdMode( tdMODELVIEW );
tdIdentity( gSMatrix );
tdTranslate( gSMatrix, 0, 0, -40 );
tdLookAt( gSMatrix, eye, at, up );
for( x = 0; x < HMX-1; x++ )
for( y = 0; y < HMY-1; y++ )
{
float tx = x-HMX/2;
float ty = y-HMY/2;
float pta[3];
float ptb[3];
float ptc[3];
float ptd[3];
float normal[3];
float lightdir[3] = { 1, -1, 1 };
float tmp1[3];
float tmp2[3];
RDPoint pto[6];
pta[0] = tx+0; pta[1] = ty+0; pta[2] = Heightmap[(x+0)+(y+0)*HMX];
ptb[0] = tx+1; ptb[1] = ty+0; ptb[2] = Heightmap[(x+1)+(y+0)*HMX];
ptc[0] = tx+0; ptc[1] = ty+1; ptc[2] = Heightmap[(x+0)+(y+1)*HMX];
ptd[0] = tx+1; ptd[1] = ty+1; ptd[2] = Heightmap[(x+1)+(y+1)*HMX];
tdPSub( pta, ptb, tmp2 );
tdPSub( ptc, ptb, tmp1 );
tdCross( tmp1, tmp2, normal );
tdFinalPoint( pta, pta );
tdFinalPoint( ptb, ptb );
tdFinalPoint( ptc, ptc );
tdFinalPoint( ptd, ptd );
if( pta[2] >= 1.0 ) continue;
if( ptb[2] >= 1.0 ) continue;
if( ptc[2] >= 1.0 ) continue;
if( ptd[2] >= 1.0 ) continue;
if( pta[2] < 0 ) continue;
if( ptb[2] < 0 ) continue;
if( ptc[2] < 0 ) continue;
if( ptd[2] < 0 ) continue;
pto[0].x = pta[0]; pto[0].y = pta[1];
pto[1].x = ptb[0]; pto[1].y = ptb[1];
pto[2].x = ptd[0]; pto[2].y = ptd[1];
pto[3].x = ptc[0]; pto[3].y = ptc[1];
pto[4].x = ptd[0]; pto[4].y = ptd[1];
pto[5].x = pta[0]; pto[5].y = pta[1];
float bright = tdDot( normal, lightdir );
if( bright < 0 ) bright = 0;
CNFGColor( 0xff000000 | (int)( bright * 50 ) );
CNFGTackSegment( pta[0], pta[1], ptb[0], ptb[1] );
CNFGTackSegment( pta[0], pta[1], ptc[0], ptc[1] );
}
}

View file

@ -0,0 +1,206 @@
/*
* Copyright (c) 2021 zNoctum
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
* software and associated documentation files (the "Software"), to deal in the Software
* without restriction, including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
* to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
* FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct table_entry {
size_t keysize;
char *key;
char *value;
};
#define SWALLOW 1
char *readfile(const char *filename, int flags)
{
size_t size;
char *buffer;
FILE *file = fopen(filename, "r");
if (file == NULL) {
printf("Failed to open '%s'!\n", filename);
exit(1);
}
fseek(file, 0, SEEK_END);
size = ftell(file);
rewind(file);
buffer = malloc(size);
fread(buffer, size, 1, file);
if ((flags & SWALLOW) != 0) {
if (buffer[size - 1] == '\n')
buffer[size - 1] = 0x00;
}
fclose(file);
return buffer;
}
void print_help()
{
printf(
"Usage: subst [TEMPLATE-FILE] [VARIABLES]\n"
"\n"
"Options:\n"
" -s: Swallow following newline in files\n"
" -o: Define output file\n"
" -f: Define variable to replace '-f VARNAME FILEPATH'\n"
" -d: Define variable to replace '-f VARNAME VARCONTENT'\n"
" -h: Print this message\n"
);
}
char *outfile_name = NULL;
char *outfile = NULL;
size_t outfile_size;
size_t outfile_current_size;
int outfile_set = 0;
void putchar_in_output(char c)
{
if (outfile_set) {
if (outfile == NULL) {
outfile = malloc(sizeof(char) * 1024);
outfile_size = 1024;
}
if (outfile_current_size == outfile_size) {
outfile_size *= 2;
outfile = realloc(outfile, outfile_size);
}
outfile[outfile_current_size++] = c;
} else {
putchar(c);
fflush(stdout);
}
}
int main(int argc, char *argv[])
{
int flags = 0;
size_t i;
size_t table_counter = 0;
struct table_entry *table = malloc((argc - 2) / 3 * sizeof(struct table_entry));
char *infile;
char *ident_begin;
char *table_str;
size_t ident_len;
if (argc < 2) {
printf("No input file was specified\n");
exit(1);
}
if (argv[1][0] == '-' && argv[1][1] == 'h') {
print_help();
return 0;
}
infile = readfile(argv[1], 0);
read_args:
for (i = 2; i < argc; i++) {
if (argv[i][0] == '-') {
switch(argv[i][1]) {
case 's':
if ((flags & SWALLOW) == 0) {
flags |= SWALLOW;
goto read_args;
}
break;
case 'o':
outfile_name = argv[++i];
outfile_set = 1;
break;
case 'd':
if (i++ == argc) {
printf("supplied not enough arguments\n");
}
table[table_counter].keysize = strlen(argv[i]);
table[table_counter].key = argv[i];
i++;
table[table_counter].value = argv[i];
table_counter++;
break;
case 'f':
if (i++ == argc) {
printf("supplied not enough arguments\n");
}
table[table_counter].keysize = strlen(argv[i]);
table[table_counter].key = argv[i];
i++;
table[table_counter].value = readfile(argv[i], flags);
table_counter++;
break;
case 'h':
print_help();
return 0;
}
}
}
while (*infile) {
if (!(*infile == '$' && infile[1] == '{')) {
putchar_in_output(*infile);
infile++;
continue;
}
infile += 2;
ident_begin = infile;
ident_len = 0;
while (*infile != '}') {
if (*infile == 0x00) {
printf("Unclosed variable refrence!\n");
exit(1);
}
infile++;
ident_len++;
}
for (i = 0; i < table_counter; i++) {
if (table[i].keysize != ident_len)
continue;
if (strncmp(table[i].key, ident_begin, ident_len) != 0)
continue;
table_str = table[i].value;
while (*table_str)
putchar_in_output(*table_str++);
}
infile++;
}
if (outfile_set) {
FILE* file = fopen(outfile_name, "w");
if (file == NULL) {
printf("Failed to open %s for output!\n", outfile_name);
exit(1);
}
fwrite(outfile, outfile_current_size - 3, 1, file);
fclose(file);
}
return 0;
}

View file

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Test</title>
<style>
body { background-color: #000080; }
document : {overflow: hidden; width=100%; height=100%;}
</style>
</head>
<body>
<div STYLE="position:absolute;width:640;z-index:10;color:white;a:red">
This is a demonstration of rawdrawwasm, a minimal technique to use C with rawdraw with WASM and without Emscripten.
This file is a single-file HTML file. Thanks to @zNoctum and @redline2466. Check out the info surrounding this project <A HREF=https://github.com/cnlohr/rawdrawwasm/ style="color:red">here</a>. This is now part of <a href=https://github.com/cntools/rawdraw style="color:red">rawdraw</a>.
</div>
<div STYLE="positon:absolute;z-index:3">
<canvas width=500 height=500 id=canvas></canvas>
</div>
<script>
${JAVASCRIPT_DATA}
</script>
</body>
</html>

View file

@ -0,0 +1,314 @@
//Portions of code from zNoctum and redline2466
//Global memory for application.
let memory = new WebAssembly.Memory({initial:200});
let HEAP8 = new Int8Array(memory.buffer);
let HEAPU8 = new Uint8Array(memory.buffer);
let HEAP16 = new Int16Array(memory.buffer);
let HEAPU16 = new Uint16Array(memory.buffer);
let HEAP32 = new Uint32Array(memory.buffer);
let HEAPU32 = new Uint32Array(memory.buffer);
let HEAPF32 = new Float32Array(memory.buffer);
let HEAPF64 = new Float64Array(memory.buffer);
let toUtf8Decoder = new TextDecoder( "utf-8" );
function toUTF8(ptr) {
let len = 0|0; ptr |= 0;
for( let i = ptr; HEAPU8[i] != 0; i++) len++;
return toUtf8Decoder.decode(HEAPU8.subarray(ptr, ptr+len));
}
let wasmExports;
const DATA_ADDR = 16|0; // Where the unwind/rewind data structure will live.
let rendering = false;
let fullscreen = false;
//Configure WebGL Stuff (allow to be part of global context)
let canvas = document.getElementById('canvas');
let wgl = canvas.getContext('webgl');
if( !wgl )
{
//Janky - on Firefox 83, with NVIDIA GPU, you need to ask twice.
wgl = canvas.getContext('webgl');
}
let wglShader = null; //Standard flat color shader
let wglABV = null; //Array buffer for vertices
let wglABC = null; //Array buffer for colors.
let wglUXFRM = null; //Uniform location for transform on solid colors
//Utility stuff for WebGL sahder creation.
function wgl_makeShader( vertText, fragText )
{
let vert = wgl.createShader(wgl.VERTEX_SHADER);
wgl.shaderSource(vert, vertText );
wgl.compileShader(vert);
if (!wgl.getShaderParameter(vert, wgl.COMPILE_STATUS)) {
alert(wgl.getShaderInfoLog(vert));
}
let frag = wgl.createShader(wgl.FRAGMENT_SHADER);
wgl.shaderSource(frag, fragText );
wgl.compileShader(frag);
if (!wgl.getShaderParameter(frag, wgl.COMPILE_STATUS)) {
alert(wgl.getShaderInfoLog(frag));
}
let ret = wgl.createProgram();
wgl.attachShader(ret, frag);
wgl.attachShader(ret, vert);
wgl.linkProgram(ret);
wgl.bindAttribLocation( ret, 0, "a0" );
wgl.bindAttribLocation( ret, 1, "a1" );
return ret;
}
{
//We load two shaders, one is a solid-color shader, for most rawdraw objects.
wglShader = wgl_makeShader(
"uniform vec4 xfrm; attribute vec3 a0; attribute vec4 a1; varying vec4 vc; void main() { gl_Position = vec4( a0.xy*xfrm.xy+xfrm.zw, a0.z, 0.5 ); vc = a1; }",
"precision mediump float; varying vec4 vc; void main() { gl_FragColor = vec4(vc.xyzw); }" );
wglUXFRM = wgl.getUniformLocation(wglShader, "xfrm" );
//Compile the shaders.
wgl.useProgram(wglShader);
//Get some vertex/color buffers, to put geometry in.
wglABV = wgl.createBuffer();
wglABC = wgl.createBuffer();
//We're using two buffers, so just enable them, now.
wgl.enableVertexAttribArray(0);
wgl.enableVertexAttribArray(1);
//Enable alpha blending
wgl.enable(wgl.BLEND);
wgl.blendFunc(wgl.SRC_ALPHA, wgl.ONE_MINUS_SRC_ALPHA);
}
//Do webgl work that must happen every frame.
function FrameStart()
{
//Fixup canvas sizes
if( fullscreen )
{
wgl.viewportWidth = canvas.width = window.innerWidth;
wgl.viewportHeight = canvas.height = window.innerHeight;
}
//Make sure viewport and input to shader is correct.
//We do this so we can pass literal coordinates into the shader.
wgl.viewport( 0, 0, wgl.viewportWidth, wgl.viewportHeight );
//Update geometry transform (Scale/shift)
wgl.uniform4f( wglUXFRM,
1./wgl.viewportWidth, -1./wgl.viewportHeight,
-0.5, 0.5);
}
function SystemStart( title, w, h )
{
document.title = toUTF8( title );
wgl.viewportWidth = canvas.width = w;
wgl.viewportHeight = canvas.height = h;
}
//Buffered geometry system.
//This handles buffering a bunch of lines/segments, and using them all at once.
globalv = null;
function CNFGEmitBackendTrianglesJS( vertsF, colorsI, vertcount )
{
const ab = wgl.ARRAY_BUFFER;
wgl.bindBuffer(ab, wglABV);
wgl.bufferData(ab, vertsF, wgl.DYNAMIC_DRAW);
wgl.vertexAttribPointer(0, 3, wgl.FLOAT, false, 0, 0);
wgl.bindBuffer(ab, wglABC);
wgl.bufferData(ab, colorsI, wgl.DYNAMIC_DRAW);
wgl.vertexAttribPointer(1, 4, wgl.UNSIGNED_BYTE, true, 0, 0);
wgl.drawArrays(wgl.TRIANGLES, 0, vertcount );
globalv = vertsF;
}
//This defines the list of imports, the things that C will be importing from Javascript.
//To use functions here, just call them. Surprisingly, signatures justwork.
let imports = {
env: {
//Mapping our array buffer into the system.
memory: memory,
//Various draw-functions.
CNFGEmitBackendTriangles : (vertsF, colorsI, vertcount )=>
{
//Take a float* and uint32_t* of vertices, and flat-render them.
CNFGEmitBackendTrianglesJS(
HEAPF32.slice(vertsF>>2,(vertsF>>2)+vertcount*3),
HEAPU8.slice(colorsI,(colorsI)+vertcount*4),
vertcount );
},
CNFGSetup : (title,w,h ) => {
SystemStart( title, w, h );
fullscreen = false;
},
CNFGSetupFullscreen : (title,w,h ) => {
SystemStart( title, w, h );
canvas.style = "position:absolute; top:0; left:0;"
fullscreen = true;
},
CNFGClearFrameInternal: ( color ) => {
wgl.clearColor( (color&0xff)/255., ((color>>8)&0xff)/255.,
((color>>16)&0xff)/255., ((color>>24)&0xff)/255. );
wgl.clear( wgl.COLOR_BUFFER_BIT | wgl.COLOR_DEPTH_BIT );
},
CNFGGetDimensions: (pw, ph) => {
HEAP16[pw>>1] = canvas.width;
HEAP16[ph>>1] = canvas.height;
},
OGGetAbsoluteTime : () => { return new Date().getTime()/1000.; },
Add1 : (i) => { return i+1; }, //Super simple function for speed testing.
//Tricky - math functions just automatically link through.
sin : Math.sin,
cos : Math.cos,
tan : Math.tan,
sinf : Math.sin,
cosf : Math.cos,
tanf : Math.tan,
//Quick-and-dirty debug.
print: console.log,
prints: (str) => { console.log(toUTF8(str)); },
}
};
if( !RAWDRAW_USE_LOOP_FUNCTION )
{
imports.bynsyncify = {
//Any javascript functions which may unwind the stack should be placed here.
CNFGSwapBuffersInternal: () => {
if (!rendering) {
// We are called in order to start a sleep/unwind.
// Fill in the data structure. The first value has the stack location,
// which for simplicity we can start right after the data structure itself.
HEAPU32[DATA_ADDR >> 2] = DATA_ADDR + 8;
// The end of the stack will not be reached here anyhow.
HEAPU32[DATA_ADDR + 4 >> 2] = 1024|0;
wasmExports.asyncify_start_unwind(DATA_ADDR);
rendering = true;
// Resume after the proper delay.
requestAnimationFrame(function() {
FrameStart();
wasmExports.asyncify_start_rewind(DATA_ADDR);
// The code is now ready to rewind; to start the process, enter the
// first function that should be on the call stack.
wasmExports.main();
});
} else {
// We are called as part of a resume/rewind. Stop sleeping.
wasmExports.asyncify_stop_rewind();
rendering = false;
}
}
}
}
if( RAWDRAW_NEED_BLITTER )
{
let wglBlit = null; //Blitting shader for texture
let wglTex = null; //Texture handle for blitting.
let wglUXFRMBlit = null; //Uniform location for transform on blitter
//We are not currently supporting the software renderer.
//We load two shaders, the other is a texture shader, for blitting things.
wglBlit = wgl_makeShader(
"uniform vec4 xfrm; attribute vec3 a0; attribute vec4 a1; varying vec2 tc; void main() { gl_Position = vec4( a0.xy*xfrm.xy+xfrm.zw, a0.z, 0.5 ); tc = a1.xy; }",
"precision mediump float; varying vec2 tc; uniform sampler2D tex; void main() { gl_FragColor = texture2D(tex,tc).wzyx;}" );
wglUXFRMBlit = wgl.getUniformLocation(wglBlit, "xfrm" );
imports.env.CNFGBlitImageInternal = (memptr, x, y, w, h ) => {
if( w <= 0 || h <= 0 ) return;
wgl.useProgram(wglBlit);
//Most of the time we don't use textures, so don't initiate at start.
if( wglTex == null ) wglTex = wgl.createTexture();
wgl.activeTexture(wgl.TEXTURE0);
const t2d = wgl.TEXTURE_2D;
wgl.bindTexture(t2d, wglTex);
//Note that unlike the normal color operation, we don't have an extra offset.
wgl.uniform4f( wglUXFRMBlit,
1./wgl.viewportWidth, -1./wgl.viewportHeight,
-.5+x/wgl.viewportWidth, .5-y/wgl.viewportHeight );
//These parameters are required. Not sure why the defaults don't work.
wgl.texParameteri(t2d, wgl.TEXTURE_WRAP_T, wgl.CLAMP_TO_EDGE);
wgl.texParameteri(t2d, wgl.TEXTURE_WRAP_S, wgl.CLAMP_TO_EDGE);
wgl.texParameteri(t2d, wgl.TEXTURE_MIN_FILTER, wgl.NEAREST);
wgl.texImage2D(t2d, 0, wgl.RGBA, w, h, 0, wgl.RGBA,
wgl.UNSIGNED_BYTE, new Uint8Array(memory.buffer,memptr,w*h*4) );
CNFGEmitBackendTrianglesJS(
new Float32Array( [0,0,0, w,0,0, w,h,0, 0,0,0, w,h,0, 0,h,0 ] ),
new Uint8Array( [0,0,0,0, 255,0,0,0, 255,255,0,0, 0,0,0,0, 255,255,0,0, 0,255,0,0] ),
6 );
wgl.useProgram(wglShader);
};
}
{
// Actually load the WASM blob.
let blob = atob('${BLOB}');
let array = new Uint8Array(new ArrayBuffer(blob.length));
for(let i = 0; i < blob.length; i++) array[i] = blob.charCodeAt(i);
WebAssembly.instantiate(array, imports).then(
function(wa) {
instance = wa.instance;
wasmExports = instance.exports;
//Attach inputs.
if( instance.exports.HandleMotion )
{
canvas.addEventListener('mousemove', e => { instance.exports.HandleMotion( e.offsetX, e.offsetY, e.buttons ); } );
canvas.addEventListener('touchmove', e => { instance.exports.HandleMotion( e.touches[0].clientX, e.touches[0].clientY, 1 ); } );
}
if( instance.exports.HandleButton )
{
canvas.addEventListener('mouseup', e => { instance.exports.HandleButton( e.offsetX, e.offsetY, e.button, 0 ); return false; } );
canvas.addEventListener('mousedown', e => { instance.exports.HandleButton( e.offsetX, e.offsetY, e.button, 1 ); return false; } );
}
if( instance.exports.HandleKey )
{
document.addEventListener('keydown', e => { instance.exports.HandleKey( e.keyCode, 1 ); } );
document.addEventListener('keyup', e => { instance.exports.HandleKey( e.keyCode, 0 ); } );
}
//Actually invoke main(). Note that, upon "CNFGSwapBuffers" this will 'exit'
//But, will get re-entered from the swapbuffers animation callback.
instance.exports.main();
if( RAWDRAW_USE_LOOP_FUNCTION )
{
function floop() {
FrameStart();
requestAnimationFrame(floop);
// The code is now ready to rewind; to start the process, enter the
// first function that should be on the call stack.
wasmExports.loop();
}
floop();
}
} );
//Code here would continue executing, but this code is executed *before* main.
}

663
app/src/nativecode/test.c Normal file
View file

@ -0,0 +1,663 @@
//Copyright (c) 2011-2020 <>< Charles Lohr - Under the MIT/x11 or NewBSD License you choose.
// NO WARRANTY! NO GUARANTEE OF SUPPORT! USE AT YOUR OWN RISK
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include "os_generic.h"
#include <GLES3/gl3.h>
#include <android/asset_manager.h>
#include <android/asset_manager_jni.h>
#include <android_native_app_glue.h>
#include <android/sensor.h>
#include <byteswap.h>
#include <errno.h>
#include <fcntl.h>
#include "CNFGAndroid.h"
//#define CNFA_IMPLEMENTATION
#define CNFG_IMPLEMENTATION
#define CNFG3D
//#include "cnfa/CNFA.h"
#include "CNFG.h"
#define WEBVIEW_NATIVE_ACTIVITY_IMPLEMENTATION
#include "webview_native_activity.h"
float mountainangle;
float mountainoffsetx;
float mountainoffsety;
ASensorManager * sm;
const ASensor * as;
bool no_sensor_for_gyro = false;
ASensorEventQueue* aeq;
ALooper * l;
WebViewNativeActivityObject MyWebView;
const uint32_t SAMPLE_RATE = 44100;
const uint16_t SAMPLE_COUNT = 512;
uint32_t stream_offset = 0;
uint16_t audio_frequency;
void SetupIMU()
{
sm = ASensorManager_getInstanceForPackage("gyroscope");
as = ASensorManager_getDefaultSensor( sm, ASENSOR_TYPE_GYROSCOPE );
no_sensor_for_gyro = as == NULL;
l = ALooper_prepare( ALOOPER_PREPARE_ALLOW_NON_CALLBACKS );
aeq = ASensorManager_createEventQueue( sm, (ALooper*)&l, 0, 0, 0 ); //XXX??!?! This looks wrong.
if(!no_sensor_for_gyro) {
ASensorEventQueue_enableSensor( aeq, as);
printf( "setEvent Rate: %d\n", ASensorEventQueue_setEventRate( aeq, as, 10000 ) );
}
}
float accx, accy, accz;
int accs;
void AccCheck()
{
if(no_sensor_for_gyro) {
return;
}
ASensorEvent evt;
do
{
ssize_t s = ASensorEventQueue_getEvents( aeq, &evt, 1 );
if( s <= 0 ) break;
accx = evt.vector.v[0];
accy = evt.vector.v[1];
accz = evt.vector.v[2];
mountainangle /*degrees*/ -= accz;// * 3.1415 / 360.0;// / 100.0;
mountainoffsety += accy;
mountainoffsetx += accx;
accs++;
} while( 1 );
}
unsigned frames = 0;
unsigned long iframeno = 0;
void AndroidDisplayKeyboard(int pShow);
int lastbuttonx = 0;
int lastbuttony = 0;
int lastmotionx = 0;
int lastmotiony = 0;
int lastbid = 0;
int lastmask = 0;
int lastkey, lastkeydown;
static int keyboard_up;
uint8_t buttonstate[8];
void HandleKey( int keycode, int bDown )
{
lastkey = keycode;
lastkeydown = bDown;
if( keycode == 10 && !bDown ) { keyboard_up = 0; AndroidDisplayKeyboard( keyboard_up ); }
if( keycode == 4 ) { AndroidSendToBack( 1 ); } //Handle Physical Back Button.
}
void HandleButton( int x, int y, int button, int bDown )
{
buttonstate[button] = bDown;
lastbid = button;
lastbuttonx = x;
lastbuttony = y;
if( bDown ) { keyboard_up = !keyboard_up; AndroidDisplayKeyboard( keyboard_up ); }
}
void HandleMotion( int x, int y, int mask )
{
lastmask = mask;
lastmotionx = x;
lastmotiony = y;
}
//writes the text to a file to path (example): /storage/emulated/0/Android/data/org.yourorg.cnfgtest/files
// You would not normally want to do this, but it's an example of how to do local storage.
void Log(const char *fmt, ...)
{
const char* getpath = AndroidGetExternalFilesDir();
char buffer[2048];
snprintf(buffer, sizeof(buffer), "%s/log.txt", getpath);
FILE *f = fopen(buffer, "w");
if (f == NULL)
{
exit(1);
}
va_list arg;
va_start(arg, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, arg);
va_end(arg);
fprintf(f, "%s\n", buffer);
fclose(f);
}
#define HMX 162
#define HMY 162
short screenx, screeny;
float Heightmap[HMX*HMY];
extern struct android_app * gapp;
void DrawHeightmap()
{
int x, y;
//float fdt = ((iframeno++)%(360*10))/10.0;
mountainangle += .2;
if( mountainangle < 0 ) mountainangle += 360;
if( mountainangle > 360 ) mountainangle -= 360;
mountainoffsety = mountainoffsety - ((mountainoffsety-100) * .1);
float eye[3] = { (float)(sin(mountainangle*(3.14159/180.0))*30*sin(mountainoffsety/100.)), (float)(cos(mountainangle*(3.14159/180.0))*30*sin(mountainoffsety/100.)), (float)(30*cos(mountainoffsety/100.)) };
float at[3] = { 0,0, 0 };
float up[3] = { 0,0, 1 };
tdSetViewport( -1, -1, 1, 1, screenx, screeny );
tdMode( tdPROJECTION );
tdIdentity( gSMatrix );
tdPerspective( 30, ((float)screenx)/((float)screeny), .1, 200., gSMatrix );
tdMode( tdMODELVIEW );
tdIdentity( gSMatrix );
tdTranslate( gSMatrix, 0, 0, -40 );
tdLookAt( gSMatrix, eye, at, up );
float scale = 60./HMX;
for( x = 0; x < HMX-1; x++ )
for( y = 0; y < HMY-1; y++ )
{
float tx = x-HMX/2;
float ty = y-HMY/2;
float pta[3];
float ptb[3];
float ptc[3];
float ptd[3];
float normal[3];
float lightdir[3] = { .6, -.6, 1 };
float tmp1[3];
float tmp2[3];
RDPoint pto[6];
pta[0] = (tx+0)*scale; pta[1] = (ty+0)*scale; pta[2] = Heightmap[(x+0)+(y+0)*HMX]*scale;
ptb[0] = (tx+1)*scale; ptb[1] = (ty+0)*scale; ptb[2] = Heightmap[(x+1)+(y+0)*HMX]*scale;
ptc[0] = (tx+0)*scale; ptc[1] = (ty+1)*scale; ptc[2] = Heightmap[(x+0)+(y+1)*HMX]*scale;
ptd[0] = (tx+1)*scale; ptd[1] = (ty+1)*scale; ptd[2] = Heightmap[(x+1)+(y+1)*HMX]*scale;
tdPSub( pta, ptb, tmp2 );
tdPSub( ptc, ptb, tmp1 );
tdCross( tmp1, tmp2, normal );
tdNormalizeSelf( normal );
tdFinalPoint( pta, pta );
tdFinalPoint( ptb, ptb );
tdFinalPoint( ptc, ptc );
tdFinalPoint( ptd, ptd );
if( pta[2] >= 1.0 ) continue;
if( ptb[2] >= 1.0 ) continue;
if( ptc[2] >= 1.0 ) continue;
if( ptd[2] >= 1.0 ) continue;
if( pta[2] < 0 ) continue;
if( ptb[2] < 0 ) continue;
if( ptc[2] < 0 ) continue;
if( ptd[2] < 0 ) continue;
pto[0].x = pta[0]; pto[0].y = pta[1];
pto[1].x = ptb[0]; pto[1].y = ptb[1];
pto[2].x = ptd[0]; pto[2].y = ptd[1];
pto[3].x = ptc[0]; pto[3].y = ptc[1];
pto[4].x = ptd[0]; pto[4].y = ptd[1];
pto[5].x = pta[0]; pto[5].y = pta[1];
// CNFGColor(((x+y)&1)?0xFFFFFF:0x000000);
float bright = tdDot( normal, lightdir );
if( bright < 0 ) bright = 0;
CNFGColor( 0xff | ( ( (int)( bright * 90 ) ) << 24 ) );
// CNFGTackPoly( &pto[0], 3 ); CNFGTackPoly( &pto[3], 3 );
CNFGTackSegment( pta[0], pta[1], ptb[0], ptb[1] );
CNFGTackSegment( pta[0], pta[1], ptc[0], ptc[1] );
CNFGTackSegment( ptb[0], ptb[1], ptc[0], ptc[1] );
}
}
int HandleDestroy()
{
printf( "Destroying\n" );
return 0;
}
volatile int suspended;
void HandleSuspend()
{
suspended = 1;
}
void HandleResume()
{
suspended = 0;
}
/*
void AudioCallback( struct CNFADriver * sd, short * out, short * in, int framesp, int framesr )
{
memset(out, 0, framesp*sizeof(uint16_t));
if(suspended) return;
if(!buttonstate[1]) return; // play audio only if ~touching with two fingers
audio_frequency = 440;
for(uint32_t i = 0; i < framesp; i++) {
int16_t sample = INT16_MAX * sin(audio_frequency*(2*M_PI)*(stream_offset+i)/SAMPLE_RATE);
out[i] = sample;
}
stream_offset += framesp;
}
*/
void MakeNotification( const char * channelID, const char * channelName, const char * title, const char * message )
{
static int id;
id++;
const struct JNINativeInterface * env = 0;
const struct JNINativeInterface ** envptr = &env;
const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm;
const struct JNIInvokeInterface * jnii = *jniiptr;
jnii->AttachCurrentThread( jniiptr, &envptr, NULL);
env = (*envptr);
jstring channelIDStr = env->NewStringUTF( ENVCALL channelID );
jstring channelNameStr = env->NewStringUTF( ENVCALL channelName );
// Runs getSystemService(Context.NOTIFICATION_SERVICE).
jclass NotificationManagerClass = env->FindClass( ENVCALL "android/app/NotificationManager" );
jclass activityClass = env->GetObjectClass( ENVCALL gapp->activity->clazz );
jmethodID MethodGetSystemService = env->GetMethodID( ENVCALL activityClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
jstring notificationServiceName = env->NewStringUTF( ENVCALL "notification" );
jobject notificationServiceObj = env->CallObjectMethod( ENVCALL gapp->activity->clazz, MethodGetSystemService, notificationServiceName);
// create the Notification channel.
jclass notificationChannelClass = env->FindClass( ENVCALL "android/app/NotificationChannel" );
jmethodID notificationChannelConstructorID = env->GetMethodID( ENVCALL notificationChannelClass, "<init>", "(Ljava/lang/String;Ljava/lang/CharSequence;I)V" );
jobject notificationChannelObj = env->NewObject( ENVCALL notificationChannelClass, notificationChannelConstructorID, channelIDStr, channelNameStr, 3 ); // IMPORTANCE_DEFAULT
jmethodID createNotificationChannelID = env->GetMethodID( ENVCALL NotificationManagerClass, "createNotificationChannel", "(Landroid/app/NotificationChannel;)V" );
env->CallVoidMethod( ENVCALL notificationServiceObj, createNotificationChannelID, notificationChannelObj );
env->DeleteLocalRef( ENVCALL channelNameStr );
env->DeleteLocalRef( ENVCALL notificationChannelObj );
// Create the Notification builder.
jclass classBuilder = env->FindClass( ENVCALL "android/app/Notification$Builder" );
jstring titleStr = env->NewStringUTF( ENVCALL title );
jstring messageStr = env->NewStringUTF( ENVCALL message );
jmethodID eventConstructor = env->GetMethodID( ENVCALL classBuilder, "<init>", "(Landroid/content/Context;Ljava/lang/String;)V" );
jobject eventObj = env->NewObject( ENVCALL classBuilder, eventConstructor, gapp->activity->clazz, channelIDStr );
jmethodID setContentTitleID = env->GetMethodID( ENVCALL classBuilder, "setContentTitle", "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;" );
jmethodID setContentTextID = env->GetMethodID( ENVCALL classBuilder, "setContentText", "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;" );
jmethodID setSmallIconID = env->GetMethodID( ENVCALL classBuilder, "setSmallIcon", "(I)Landroid/app/Notification$Builder;" );
// You could do things like setPriority, or setContentIntent if you want it to do something when you click it.
env->CallObjectMethod( ENVCALL eventObj, setContentTitleID, titleStr );
env->CallObjectMethod( ENVCALL eventObj, setContentTextID, messageStr );
env->CallObjectMethod( ENVCALL eventObj, setSmallIconID, 17301504 ); // R.drawable.alert_dark_frame
// eventObj.build()
jmethodID buildID = env->GetMethodID( ENVCALL classBuilder, "build", "()Landroid/app/Notification;" );
jobject notification = env->CallObjectMethod( ENVCALL eventObj, buildID );
// NotificationManager.notify(...)
jmethodID notifyID = env->GetMethodID( ENVCALL NotificationManagerClass, "notify", "(ILandroid/app/Notification;)V" );
env->CallVoidMethod( ENVCALL notificationServiceObj, notifyID, id, notification );
env->DeleteLocalRef( ENVCALL notification );
env->DeleteLocalRef( ENVCALL titleStr );
env->DeleteLocalRef( ENVCALL activityClass );
env->DeleteLocalRef( ENVCALL messageStr );
env->DeleteLocalRef( ENVCALL channelIDStr );
env->DeleteLocalRef( ENVCALL NotificationManagerClass );
env->DeleteLocalRef( ENVCALL notificationServiceObj );
env->DeleteLocalRef( ENVCALL notificationServiceName );
}
void HandleThisWindowTermination()
{
suspended = 1;
}
uint32_t randomtexturedata[256*256];
uint32_t webviewdata[500*500];
char fromJSBuffer[128];
void CheckWebView( void * v )
{
static int runno = 0;
WebViewNativeActivityObject * wvn = (WebViewNativeActivityObject*)v;
if( WebViewGetProgress( wvn ) != 100 ) return;
runno++;
if( runno == 1 )
{
// The attach (initial) message payload has no meaning.
WebViewPostMessage( wvn, "", 1 );
}
else
{
// Invoke JavaScript, which calls a function to send a webmessage
// back into C land.
WebViewExecuteJavascript( wvn, "SendMessageToC();" );
// Send a WebMessage into the JavaScript code.
char st[128];
sprintf( st, "Into JavaScript %d\n", runno );
WebViewPostMessage( wvn, st, 0 );
}
}
jobject g_attachLooper;
void SetupWebView( void * v )
{
WebViewNativeActivityObject * wvn = (WebViewNativeActivityObject*)v;
const struct JNINativeInterface * env = 0;
const struct JNINativeInterface ** envptr = &env;
const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm;
const struct JNIInvokeInterface * jnii = *jniiptr;
jnii->AttachCurrentThread( jniiptr, &envptr, NULL);
env = (*envptr);
while( g_attachLooper == 0 ) usleep(1);
WebViewCreate( wvn, "file:///android_asset/index.html", g_attachLooper, 500, 500 );
//WebViewCreate( wvn, "about:blank", g_attachLooper, 500, 500 );
}
pthread_t jsthread;
void * JavscriptThread( void * v )
{
const struct JNINativeInterface * env = 0;
const struct JNINativeInterface ** envptr = &env;
const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm;
const struct JNIInvokeInterface * jnii = *jniiptr;
jnii->AttachCurrentThread( jniiptr, &envptr, NULL);
env = (*envptr);
// Create a looper on this thread...
jclass LooperClass = env->FindClass(envptr, "android/os/Looper");
jmethodID myLooperMethod = env->GetStaticMethodID(envptr, LooperClass, "myLooper", "()Landroid/os/Looper;");
jobject thisLooper = env->CallStaticObjectMethod( envptr, LooperClass, myLooperMethod );
if( !thisLooper )
{
jmethodID prepareMethod = env->GetStaticMethodID(envptr, LooperClass, "prepare", "()V");
env->CallStaticVoidMethod( envptr, LooperClass, prepareMethod );
thisLooper = env->CallStaticObjectMethod( envptr, LooperClass, myLooperMethod );
g_attachLooper = env->NewGlobalRef(envptr, thisLooper);
}
jmethodID getQueueMethod = env->GetMethodID( envptr, LooperClass, "getQueue", "()Landroid/os/MessageQueue;" );
jobject lque = env->CallObjectMethod( envptr, g_attachLooper, getQueueMethod );
jclass MessageQueueClass = env->FindClass(envptr, "android/os/MessageQueue");
jmethodID nextMethod = env->GetMethodID( envptr, MessageQueueClass, "next", "()Landroid/os/Message;" );
jclass MessageClass = env->FindClass(envptr, "android/os/Message");
jfieldID objid = env->GetFieldID( envptr, MessageClass, "obj", "Ljava/lang/Object;" );
jclass PairClass = env->FindClass(envptr, "android/util/Pair");
jfieldID pairfirst = env->GetFieldID( envptr, PairClass, "first", "Ljava/lang/Object;" );
while(1)
{
// Instead of using Looper::loop(), we just call next on the looper object.
jobject msg = env->CallObjectMethod( envptr, lque, nextMethod );
jobject innerObj = env->GetObjectField( envptr, msg, objid );
const char * name;
jstring strObj;
jclass innerClass;
// Check Object Type
{
innerClass = env->GetObjectClass( envptr, innerObj );
jmethodID mid = env->GetMethodID( envptr, innerClass, "getClass", "()Ljava/lang/Class;");
jobject clsObj = env->CallObjectMethod( envptr, innerObj, mid );
jclass clazzz = env->GetObjectClass( envptr, clsObj );
mid = env->GetMethodID(envptr, clazzz, "getName", "()Ljava/lang/String;");
strObj = (jstring)env->CallObjectMethod( envptr, clsObj, mid);
name = env->GetStringUTFChars( envptr, strObj, 0);
env->DeleteLocalRef( envptr, clsObj );
env->DeleteLocalRef( envptr, clazzz );
}
if( strcmp( name, "z5" ) == 0 )
{
// Special, Some Androids (notably Meta Quest) use a different private message type.
jfieldID mstrf = env->GetFieldID( envptr, innerClass, "a", "[B" );
jbyteArray jba = (jstring)env->GetObjectField(envptr, innerObj, mstrf );
int len = env->GetArrayLength( envptr, jba );
jboolean isCopy = 0;
jbyte * bufferPtr = env->GetByteArrayElements(envptr, jba, &isCopy);
if( len >= 6 )
{
const char *descr = (const char*)bufferPtr + 6;
char tcpy[len-5];
memcpy( tcpy, descr, len-6 );
tcpy[len-6] = 0;
snprintf( fromJSBuffer, sizeof( fromJSBuffer)-1, "WebMessage: %s\n", tcpy );
env->DeleteLocalRef( envptr, jba );
}
}
else
{
jobject MessagePayload = env->GetObjectField( envptr, innerObj, pairfirst );
// MessagePayload is a org.chromium.content_public.browser.MessagePayload
jclass mpclass = env->GetObjectClass( envptr, MessagePayload );
// Get field "b" which is the web message payload.
// If you are using binary sockets, it will be in `c` and be a byte array.
jfieldID mstrf = env->GetFieldID( envptr, mpclass, "b", "Ljava/lang/String;" );
jstring strObjDescr = (jstring)env->GetObjectField(envptr, MessagePayload, mstrf );
const char *descr = env->GetStringUTFChars( envptr, strObjDescr, 0);
snprintf( fromJSBuffer, sizeof( fromJSBuffer)-1, "WebMessage: %s\n", descr );
env->ReleaseStringUTFChars(envptr, strObjDescr, descr);
env->DeleteLocalRef( envptr, strObjDescr );
env->DeleteLocalRef( envptr, MessagePayload );
env->DeleteLocalRef( envptr, mpclass );
}
env->ReleaseStringUTFChars(envptr, strObj, name);
env->DeleteLocalRef( envptr, strObj );
env->DeleteLocalRef( envptr, msg );
env->DeleteLocalRef( envptr, innerObj );
env->DeleteLocalRef( envptr, innerClass );
}
}
void SetupJSThread()
{
pthread_create( &jsthread, 0, JavscriptThread, 0 );
}
int main( int argc, char ** argv )
{
int x, y;
double ThisTime;
double LastFPSTime = OGGetAbsoluteTime();
Log( "Starting Up" );
CNFGBGColor = 0x000040ff;
CNFGSetupFullscreen( "Test Bench", 0 );
HandleWindowTermination = HandleThisWindowTermination;
for( x = 0; x < HMX; x++ )
for( y = 0; y < HMY; y++ )
{
Heightmap[x+y*HMX] = tdPerlin2D( x, y )*8.;
}
const char * assettext = "Not Found";
AAsset * file = AAssetManager_open( gapp->activity->assetManager, "asset.txt", AASSET_MODE_BUFFER );
if( file )
{
size_t fileLength = AAsset_getLength(file);
char * temp = (char*)malloc( fileLength + 1);
memcpy( temp, AAsset_getBuffer( file ), fileLength );
temp[fileLength] = 0;
assettext = temp;
}
SetupIMU();
// Disabled, for now.
//InitCNFAAndroid( AudioCallback, "A Name", SAMPLE_RATE, 0, 1, 0, SAMPLE_COUNT, 0, 0, 0 );
SetupJSThread();
// Create webview and wait for its completion
RunCallbackOnUIThread( SetupWebView, &MyWebView );
while( !MyWebView.WebViewObject ) usleep(1);
Log( "Startup Complete" );
while(1)
{
int i;
iframeno++;
if( iframeno == 200 )
{
MakeNotification( "default", "rawdraw alerts", "rawdraw", "Hit frame two hundred\nNew Line" );
}
CNFGHandleInput();
AccCheck();
if( suspended ) { usleep(50000); continue; }
RunCallbackOnUIThread( (void(*)(void*))WebViewRequestRenderToCanvas, &MyWebView );
RunCallbackOnUIThread( CheckWebView, &MyWebView );
CNFGClearFrame();
CNFGColor( 0xFFFFFFFF );
CNFGGetDimensions( &screenx, &screeny );
// Mesh in background
CNFGSetLineWidth( 9 );
DrawHeightmap();
CNFGPenX = 0; CNFGPenY = 400;
CNFGColor( 0xffffffff );
CNFGDrawText( assettext, 15 );
CNFGFlushRender();
CNFGPenX = 0; CNFGPenY = 480;
char st[50];
sprintf( st, "%dx%d %d %d %d %d %d %d\n%d %d\n%5.2f %5.2f %5.2f %d", screenx, screeny, lastbuttonx, lastbuttony, lastmotionx, lastmotiony, lastkey, lastkeydown, lastbid, lastmask, accx, accy, accz, accs );
CNFGDrawText( st, 10 );
CNFGSetLineWidth( 2 );
// Square behind text
CNFGColor( 0x303030ff );
CNFGTackRectangle( 600, 0, 950, 350);
CNFGPenX = 10; CNFGPenY = 10;
// Text
CNFGColor( 0xffffffff );
for( i = 0; i < 1; i++ )
{
int c;
char tw[2] = { 0, 0 };
for( c = 0; c < 256; c++ )
{
tw[0] = c;
CNFGPenX = ( c % 16 ) * 20+606;
CNFGPenY = ( c / 16 ) * 20+5;
CNFGDrawText( tw, 4 );
}
}
// Green triangles
CNFGPenX = 0;
CNFGPenY = 0;
CNFGColor( 0x00FF00FF );
for( i = 0; i < 400; i++ )
{
RDPoint pp[3];
pp[0].x = (short)(50*sin((float)(i+iframeno)*.01) + (i%20)*30);
pp[0].y = (short)(50*cos((float)(i+iframeno)*.01) + (i/20)*20)+700;
pp[1].x = (short)(20*sin((float)(i+iframeno)*.01) + (i%20)*30);
pp[1].y = (short)(50*cos((float)(i+iframeno)*.01) + (i/20)*20)+700;
pp[2].x = (short)(10*sin((float)(i+iframeno)*.01) + (i%20)*30);
pp[2].y = (short)(30*cos((float)(i+iframeno)*.01) + (i/20)*20)+700;
CNFGTackPoly( pp, 3 );
}
// Last WebMessage
CNFGColor( 0xFFFFFFFF );
CNFGPenX = 0; CNFGPenY = 100;
CNFGDrawText( fromJSBuffer, 6 );
int x, y;
for( y = 0; y < 256; y++ )
for( x = 0; x < 256; x++ )
randomtexturedata[x+y*256] = x | ((x*394543L+y*355+iframeno*3)<<8);
CNFGBlitImage( randomtexturedata, 100, 600, 256, 256 );
WebViewNativeGetPixels( &MyWebView, webviewdata, 500, 500 );
CNFGBlitImage( webviewdata, 500, 640, 500, 500 );
frames++;
//On Android, CNFGSwapBuffers must be called, and CNFGUpdateScreenWithBitmap does not have an implied framebuffer swap.
CNFGSwapBuffers();
ThisTime = OGGetAbsoluteTime();
if( ThisTime > LastFPSTime + 1 )
{
printf( "FPS: %d\n", frames );
frames = 0;
LastFPSTime+=1;
}
}
return(0);
}

View file

@ -0,0 +1,312 @@
#ifndef _WEBVIEW_NATIVE_ACTIVITY
#define _WEBVIEW_NATIVE_ACTIVITY
#include <android/native_activity.h>
extern volatile jobject g_objRootView;
typedef struct
{
jobject WebViewObject;
jobjectArray MessageChannels;
jobject BackingBitmap;
jobject BackingCanvas;
int updated_canvas;
int w, h;
} WebViewNativeActivityObject;
// Must be called from main thread
// initial_url = "about:blank" for a java-script only page. Can also be file:///android_asset/test.html.
// Loading from "about:blank" will make the page ready almost immediately, otherwise it's about 50ms to load.
// useLooperForWebMessages is required, and must be a global jobject of your preferred looper to handle webmessages.
void WebViewCreate( WebViewNativeActivityObject * w, const char * initial_url, jobject useLooperForWebMessages, int pw, int ph );
void WebViewExecuteJavascript( WebViewNativeActivityObject * obj, const char * js );
// Note: Do not initialize until page reports as 100% loaded, with WebViewGetProgress.
void WebViewPostMessage( WebViewNativeActivityObject * obj, const char * mesg, int initial );
void WebViewRequestRenderToCanvas( WebViewNativeActivityObject * obj );
int WebViewGetProgress( WebViewNativeActivityObject * obj );
char * WebViewGetLastWindowTitle( WebViewNativeActivityObject * obj );
// Can be called from any thread.
void WebViewNativeGetPixels( WebViewNativeActivityObject * obj, uint32_t * pixel_data, int w, int h );
#ifdef WEBVIEW_NATIVE_ACTIVITY_IMPLEMENTATION
volatile jobject g_objRootView;
void WebViewCreate( WebViewNativeActivityObject * w, const char * initial_url, jobject useLooperForWebMessages, int pw, int ph )
{
const struct JNINativeInterface * env = 0;
const struct JNINativeInterface ** envptr = &env;
const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm;
jobject clazz = gapp->activity->clazz;
const struct JNIInvokeInterface * jnii = *jniiptr;
jnii->AttachCurrentThread( jniiptr, &envptr, NULL);
env = (*envptr);
if( g_objRootView == 0 )
{
jclass ViewClass = env->FindClass(envptr, "android/widget/LinearLayout");
jmethodID ViewConstructor = env->GetMethodID(envptr, ViewClass, "<init>", "(Landroid/content/Context;)V");
jclass activityClass = env->FindClass(envptr, "android/app/Activity");
jmethodID activityGetContextMethod = env->GetMethodID(envptr, activityClass, "getApplicationContext", "()Landroid/content/Context;");
jobject contextObject = env->CallObjectMethod(envptr, clazz, activityGetContextMethod);
jobject jv = env->NewObject(envptr, ViewClass, ViewConstructor, contextObject );
g_objRootView = env->NewGlobalRef(envptr, jv);
jclass clszz = env->GetObjectClass(envptr,clazz);
jmethodID setContentViewMethod = env->GetMethodID(envptr, clszz, "setContentView", "(Landroid/view/View;)V");
env->CallVoidMethod(envptr,clazz, setContentViewMethod, g_objRootView );
}
jclass WebViewClass = env->FindClass(envptr, "android/webkit/WebView");
jclass activityClass = env->FindClass(envptr, "android/app/Activity");
jmethodID activityGetContextMethod = env->GetMethodID(envptr, activityClass, "getApplicationContext", "()Landroid/content/Context;");
jobject contextObject = env->CallObjectMethod(envptr, clazz, activityGetContextMethod);
jmethodID WebViewConstructor = env->GetMethodID(envptr, WebViewClass, "<init>", "(Landroid/content/Context;)V");
jobject wvObj = env->NewObject(envptr, WebViewClass, WebViewConstructor, contextObject );
// Unknown reason why - if you don't first load about:blank, it sometimes doesn't render right?
// Even more annoying - you can't pre-use loadUrl if you want to use message channels.
jmethodID WebViewLoadBaseURLMethod = env->GetMethodID(envptr, WebViewClass, "loadDataWithBaseURL", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
jstring strul = env->NewStringUTF( envptr, "http://example.com" );
jstring strdata = env->NewStringUTF( envptr, "not-yet-loaded" );
jstring strmime = env->NewStringUTF( envptr, "text/html" );
jstring strencoding = env->NewStringUTF( envptr, "utf8" );
jstring strhistoryurl = env->NewStringUTF( envptr, "" );
env->CallVoidMethod(envptr, wvObj, WebViewLoadBaseURLMethod, strul, strdata, strmime, strencoding, strhistoryurl );
env->DeleteLocalRef( envptr, strul );
env->DeleteLocalRef( envptr, strdata );
env->DeleteLocalRef( envptr, strmime );
env->DeleteLocalRef( envptr, strencoding );
env->DeleteLocalRef( envptr, strhistoryurl );
// You have to switch to this to be able to run javascript code.
jmethodID LoadURLMethod = env->GetMethodID(envptr, WebViewClass, "loadUrl", "(Ljava/lang/String;)V");
jstring strjs = env->NewStringUTF( envptr, initial_url );
env->CallVoidMethod(envptr, wvObj, LoadURLMethod, strjs );
env->DeleteLocalRef( envptr, strjs );
jmethodID WebViewGetSettingMethod = env->GetMethodID(envptr, WebViewClass, "getSettings", "()Landroid/webkit/WebSettings;");
jobject websettings = env->CallObjectMethod(envptr, wvObj, WebViewGetSettingMethod );
jclass WebSettingsClass = env->FindClass(envptr, "android/webkit/WebSettings");
jmethodID setJavaScriptEnabledMethod = env->GetMethodID(envptr, WebSettingsClass, "setJavaScriptEnabled", "(Z)V");
env->CallVoidMethod( envptr, websettings, setJavaScriptEnabledMethod, true );
env->DeleteLocalRef( envptr, websettings );
jmethodID setMeasuredDimensionMethodID = env->GetMethodID(envptr, WebViewClass, "setMeasuredDimension", "(II)V");
env->CallVoidMethod(envptr, wvObj, setMeasuredDimensionMethodID, pw, ph );
jclass ViewClass = env->FindClass(envptr, "android/widget/LinearLayout");
jmethodID addViewMethod = env->GetMethodID(envptr, ViewClass, "addView", "(Landroid/view/View;)V");
env->CallVoidMethod( envptr, g_objRootView, addViewMethod, wvObj );
jclass WebMessagePortClass = env->FindClass(envptr, "android/webkit/WebMessagePort" );
jmethodID createWebMessageChannelMethod = env->GetMethodID(envptr, WebViewClass, "createWebMessageChannel", "()[Landroid/webkit/WebMessagePort;");
jobjectArray messageChannels = env->CallObjectMethod( envptr, wvObj, createWebMessageChannelMethod );
jobject mc0 = env->GetObjectArrayElement(envptr, messageChannels, 0); // MC1 is handed over to javascript.
jclass HandlerClassType = env->FindClass(envptr, "android/os/Handler" );
jmethodID HandlerObjectConstructor = env->GetMethodID(envptr, HandlerClassType, "<init>", "(Landroid/os/Looper;)V");
jobject handlerObject = env->NewObject( envptr, HandlerClassType, HandlerObjectConstructor, useLooperForWebMessages );
handlerObject = env->NewGlobalRef(envptr, handlerObject);
jmethodID setWebMessageCallbackMethod = env->GetMethodID( envptr, WebMessagePortClass, "setWebMessageCallback", "(Landroid/webkit/WebMessagePort$WebMessageCallback;Landroid/os/Handler;)V" );
// Only can receive messages on MC0
env->CallVoidMethod( envptr, mc0, setWebMessageCallbackMethod, 0, handlerObject );
// Generate backing bitmap and canvas.
jclass CanvasClass = env->FindClass(envptr, "android/graphics/Canvas");
jclass BitmapClass = env->FindClass(envptr, "android/graphics/Bitmap");
jmethodID createBitmap = env->GetStaticMethodID(envptr, BitmapClass, "createBitmap", "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
jclass bmpCfgCls = env->FindClass(envptr, "android/graphics/Bitmap$Config");
jstring bitmap_mode = env->NewStringUTF(envptr, "ARGB_8888");
jmethodID bmpClsValueOfMid = env->GetStaticMethodID(envptr, bmpCfgCls, "valueOf", "(Ljava/lang/String;)Landroid/graphics/Bitmap$Config;");
jobject jBmpCfg = env->CallStaticObjectMethod(envptr, bmpCfgCls, bmpClsValueOfMid, bitmap_mode);
jobject bitmap = env->CallStaticObjectMethod( envptr, BitmapClass, createBitmap, pw, ph, jBmpCfg );
jmethodID canvasConstructor = env->GetMethodID(envptr, CanvasClass, "<init>", "(Landroid/graphics/Bitmap;)V");
jobject canvas = env->NewObject(envptr, CanvasClass, canvasConstructor, bitmap );
env->DeleteLocalRef( envptr, CanvasClass );
env->DeleteLocalRef( envptr, BitmapClass );
env->DeleteLocalRef( envptr, bmpCfgCls );
env->DeleteLocalRef( envptr, bitmap_mode );
w->BackingBitmap = env->NewGlobalRef(envptr, bitmap );
w->BackingCanvas = env->NewGlobalRef(envptr, canvas );
w->WebViewObject = env->NewGlobalRef(envptr, wvObj);
w->MessageChannels = env->NewGlobalRef(envptr, messageChannels);
w->w = pw;
w->h = ph;
env->DeleteLocalRef( envptr, WebViewClass );
env->DeleteLocalRef( envptr, activityClass );
env->DeleteLocalRef( envptr, WebSettingsClass );
env->DeleteLocalRef( envptr, ViewClass );
}
int WebViewGetProgress( WebViewNativeActivityObject * obj )
{
const struct JNINativeInterface * env = 0;
const struct JNINativeInterface ** envptr = &env;
const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm;
const struct JNIInvokeInterface * jnii = *jniiptr;
jnii->AttachCurrentThread( jniiptr, &envptr, NULL);
env = (*envptr);
jclass WebViewClass = env->FindClass(envptr, "android/webkit/WebView");
jmethodID WebViewProgress = env->GetMethodID(envptr, WebViewClass, "getProgress", "()I");
int ret = env->CallIntMethod( envptr, obj->WebViewObject, WebViewProgress );
env->DeleteLocalRef( envptr, WebViewClass );
return ret;
}
void WebViewPostMessage( WebViewNativeActivityObject * w, const char * mesg, int initial )
{
const struct JNINativeInterface * env = 0;
const struct JNINativeInterface ** envptr = &env;
const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm;
const struct JNIInvokeInterface * jnii = *jniiptr;
jnii->AttachCurrentThread( jniiptr, &envptr, NULL);
env = (*envptr);
jclass WebMessagePortClass = env->FindClass(envptr, "android/webkit/WebMessagePort" );
jclass WebViewClass = env->FindClass(envptr, "android/webkit/WebView");
jclass WebMessageClass = env->FindClass(envptr, "android/webkit/WebMessage" );
jstring strjs = env->NewStringUTF( envptr, mesg );
if( initial )
{
jobject mc1 = env->GetObjectArrayElement(envptr, w->MessageChannels, 1);
jmethodID WebMessageConstructor = env->GetMethodID(envptr, WebMessageClass, "<init>", "(Ljava/lang/String;[Landroid/webkit/WebMessagePort;)V");
//https://stackoverflow.com/questions/41753104/how-do-you-use-webmessageport-as-an-alternative-to-addjavascriptinterface
// Only on initial hop do we want to post the root webmessage, which hooks up out webmessage port.
jmethodID postMessageMethod = env->GetMethodID(envptr, WebViewClass, "postWebMessage", "(Landroid/webkit/WebMessage;Landroid/net/Uri;)V");
// Need to generate a new message channel array.
jobjectArray jsUseWebPorts = env->NewObjectArray( envptr, 1, WebMessagePortClass, mc1);
// Need Uri.EMPTY
jclass UriClass = env->FindClass(envptr, "android/net/Uri" );
jfieldID EmptyField = env->GetStaticFieldID( envptr, UriClass, "EMPTY", "Landroid/net/Uri;" );
jobject EmptyURI = env->GetStaticObjectField( envptr, UriClass, EmptyField );
jobject newwm = env->NewObject(envptr, WebMessageClass, WebMessageConstructor, strjs, jsUseWebPorts );
env->CallVoidMethod( envptr, w->WebViewObject, postMessageMethod, newwm, EmptyURI );
env->DeleteLocalRef( envptr, jsUseWebPorts );
env->DeleteLocalRef( envptr, newwm );
env->DeleteLocalRef( envptr, EmptyURI );
env->DeleteLocalRef( envptr, UriClass );
}
else
{
jobject mc0 = env->GetObjectArrayElement(envptr, w->MessageChannels, 0);
jmethodID postMessageMethod = env->GetMethodID(envptr, WebMessagePortClass, "postMessage", "(Landroid/webkit/WebMessage;)V");
jmethodID WebMessageConstructor = env->GetMethodID(envptr, WebMessageClass, "<init>", "(Ljava/lang/String;)V");
jobject newwm = env->NewObject(envptr, WebMessageClass, WebMessageConstructor, strjs );
env->CallVoidMethod( envptr, mc0, postMessageMethod, newwm );
env->DeleteLocalRef( envptr, newwm );
env->DeleteLocalRef( envptr, mc0 );
}
env->DeleteLocalRef( envptr, strjs );
env->DeleteLocalRef( envptr, WebViewClass );
env->DeleteLocalRef( envptr, WebMessageClass );
env->DeleteLocalRef( envptr, WebMessagePortClass );
}
void WebViewRequestRenderToCanvas( WebViewNativeActivityObject * obj )
{
const struct JNINativeInterface * env = 0;
const struct JNINativeInterface ** envptr = &env;
const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm;
const struct JNIInvokeInterface * jnii = *jniiptr;
jnii->AttachCurrentThread( jniiptr, &envptr, NULL);
env = (*envptr);
jclass WebViewClass = env->FindClass(envptr, "android/webkit/WebView");
jmethodID drawMethod = env->GetMethodID(envptr, WebViewClass, "draw", "(Landroid/graphics/Canvas;)V");
env->CallVoidMethod( envptr, obj->WebViewObject, drawMethod, obj->BackingCanvas );
env->DeleteLocalRef( envptr, WebViewClass );
}
void WebViewNativeGetPixels( WebViewNativeActivityObject * obj, uint32_t * pixel_data, int w, int h )
{
const struct JNINativeInterface * env = 0;
const struct JNINativeInterface ** envptr = &env;
const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm;
const struct JNIInvokeInterface * jnii = *jniiptr;
jnii->AttachCurrentThread( jniiptr, &envptr, NULL);
env = (*envptr);
jclass BitmapClass = env->FindClass(envptr, "android/graphics/Bitmap");
jobject buffer = env->NewDirectByteBuffer(envptr, pixel_data, obj->w*obj->h*4 );
jmethodID copyPixelsBufferID = env->GetMethodID( envptr, BitmapClass, "copyPixelsToBuffer", "(Ljava/nio/Buffer;)V" );
env->CallVoidMethod( envptr, obj->BackingBitmap, copyPixelsBufferID, buffer );
int i;
int num = obj->w * obj->h;
for( i = 0; i < num; i++ ) pixel_data[i] = bswap_32( pixel_data[i] );
env->DeleteLocalRef( envptr, BitmapClass );
env->DeleteLocalRef( envptr, buffer );
jnii->DetachCurrentThread( jniiptr );
}
void WebViewExecuteJavascript( WebViewNativeActivityObject * obj, const char * js )
{
const struct JNINativeInterface * env = 0;
const struct JNINativeInterface ** envptr = &env;
const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm;
const struct JNIInvokeInterface * jnii = *jniiptr;
jnii->AttachCurrentThread( jniiptr, &envptr, NULL);
env = (*envptr);
jclass WebViewClass = env->FindClass(envptr, "android/webkit/WebView");
jmethodID WebViewEvalJSMethod = env->GetMethodID(envptr, WebViewClass, "evaluateJavascript", "(Ljava/lang/String;Landroid/webkit/ValueCallback;)V");
//WebView.evaluateJavascript(String script, ValueCallback<String> resultCallback)
jstring strjs = env->NewStringUTF( envptr, js );
env->CallVoidMethod( envptr, obj->WebViewObject, WebViewEvalJSMethod, strjs, 0 ); // Tricky: resultCallback = 0, if you try running looper.loop() it will crash - only manually process messages.
env->DeleteLocalRef( envptr, WebViewClass );
env->DeleteLocalRef( envptr, strjs );
}
char * WebViewGetLastWindowTitle( WebViewNativeActivityObject * obj )
{
const struct JNINativeInterface * env = 0;
const struct JNINativeInterface ** envptr = &env;
const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm;
const struct JNIInvokeInterface * jnii = *jniiptr;
jnii->AttachCurrentThread( jniiptr, &envptr, NULL);
env = (*envptr);
jclass WebViewClass = env->FindClass(envptr, "android/webkit/WebView");
jmethodID getTitle = env->GetMethodID(envptr, WebViewClass, "getTitle", "()Ljava/lang/String;");
jobject titleObject = env->CallObjectMethod( envptr, obj->WebViewObject, getTitle );
char *nativeString = strdup( env->GetStringUTFChars(envptr, titleObject, 0) );
env->DeleteLocalRef( envptr, titleObject );
env->DeleteLocalRef( envptr, WebViewClass );
return nativeString;
}
#endif
#endif

View file

@ -25,7 +25,7 @@ done
DockerfileContent(){
cat << 'DOCKERFILEEOF'
FROM debian:latest
RUN apt-get update -y && apt-get install -y make openjdk-17-jdk-headless unzip zip wget curl whiptail
RUN apt-get update -y && apt-get install -y make openjdk-17-jdk-headless unzip zip wget curl whiptail build-essential
ENV JAVA_HOME="/usr/lib/jvm/java-17-openjdk-amd64/"
ENV ANDROID_SDK_ROOT="/app/android-sdk"
ENV BUILD_TOOLS_LATEST="$ANDROID_SDK_ROOT/cmdline-tools/latest"
@ -36,7 +36,11 @@ RUN <<EOF
cat > /bin/makefile-bash-wrapper.sh << 'WRAPPER'
#!/bin/bash
printf $'\033[0;32m''#----------------------------------------\n'$'\033[0m' >&2
bash "$@"
bash -e "$@" || {
EXITCODE=$?
printf $'\033[0;31m''ERROR EXITCODE='"$EXITCODE"'\n'$'\033[0m' >&2
exit $EXITCODE
}
printf '\n\n\n\n' >&2
WRAPPER
chmod u+x /bin/makefile-bash-wrapper.sh
@ -44,6 +48,7 @@ EOF
DOCKERFILEEOF
}
printf $'\033[0;33m''$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n'$'\033[0m'
diff Dockerfile <(DockerfileContent) 2>/dev/null > /dev/null || {
test -f Dockerfile && {
read -p 'reset/start Dockerfile[Y/n]' YES