Distributed rendering stack built with .NET, MySQL, Avalonia, and simple HTTP APIs.
Consists of:
- HPCServer – lightweight render worker that runs render engines.
- RenderAPI – central queue, orchestration, and REST API.
- RNOClient – cross‑platform UI client.
- Database – MySQL schema for users, tasks, engines, and queueing.
RenderOnline is a small, self‑hosted render farm:
- Render nodes run
HPCServer, which exposes a minimal/hpc/*API and wraps CLI render engines (e.g., Blender). - A central
RenderAPIservice authenticates users, manages the queue, assigns tasks to machines, and tracks status in MySQL. - The
RNOClientGUI (Avalonia, desktop and browser targets) lets users enqueue scenes, monitor progress, download results, and manage tasks.
- Multi‑machine render farm (MySQL‑driven scheduler).
- Simple HTTP control plane:
/renderapi/v1/*for client access./hpc/*for render node control.
- Auth via
emailandtokenheaders. - Subscription‑based queue limits per user.
- Pluggable render engines:
- Engines defined in DB (
enginestable) and per‑node config (HPCServer.json). - Arguments templated with
$RENDERONLINE:<argtype>placeholders.
- Engines defined in DB (
- Safe argument validation using
argtypestable and regex/type rules. - Robust file handling:
- Upload project file via multipart/form‑data.
- Engine‑specific working directories per task.
- On‑demand ZIP packaging of result directories for download.
- Cross‑platform components:
- HPCServer and RenderAPI run on .NET.
- RNOClient runs on Windows/Linux/macOS (desktop) and browser (WebAssembly target).
HPCServer/– render node HTTP server.RenderAPI/– central API and scheduler.RNOClient/– Avalonia client:RNOClient.Desktop/– desktop entry point.RNOClient/– shared app and views (+ browser entry).
Database/– MySQL schema scripts (renderonline_*.sql).Readme/– favicons and screenshots:favicon_client.png,favicon_server.png,favicon_api.pngClient.png,Database.png
- .NET SDK 8 (or later).
- MySQL Server 8.x.
- OS:
- HPCServer & RenderAPI: Windows or Linux.
- RNOClient: Windows/Linux/macOS for desktop; any modern browser for WebAssembly target.
- A CLI render engine installed on each render node (e.g.,
blender, custom renderer, etc.).
-
Create the
renderonlinedatabase if it does not exist:CREATE DATABASE IF NOT EXISTS renderonline;
-
Import all SQL scripts from
Database/intorenderonline:mysql -u <user> -p renderonline < Database/renderonline_users.sql mysql -u <user> -p renderonline < Database/renderonline_subscriptions.sql mysql -u <user> -p renderonline < Database/renderonline_engines.sql mysql -u <user> -p renderonline < Database/renderonline_argtypes.sql mysql -u <user> -p renderonline < Database/renderonline_machines.sql mysql -u <user> -p renderonline < Database/renderonline_renders.sql mysql -u <user> -p renderonline < Database/renderonline_tasks.sql mysql -u <user> -p renderonline < Database/renderonline_queue.sql
-
Seed minimal data (adapt values to your environment):
INSERT INTO subscriptions (subscription_id, name, queue_limit) VALUES (1, 'Default', 3); INSERT INTO users (first_name, last_name, email, subscription_id, is_active, token) VALUES ('Test', 'User', 'user@example.com', 1, 1, 'my-secret-token'); INSERT INTO engines (engine_id, name, extension, download_path, render_argument) VALUES ( 1, 'blender', '.blend', '/srv/renderonline/downloads', 'blender -b $RENDERONLINE:@uploaded_file -s $RENDERONLINE:start_frame -e $RENDERONLINE:end_frame -a' ); INSERT INTO argtypes (argtype_id, type, regex) VALUES ('start_frame', 'natural', NULL), ('end_frame', 'natural', NULL), ('output_format', 'extension', NULL); INSERT INTO machines (machine_id, ip_address, port) VALUES (1, '192.168.1.10', 5001);
Notes:
engines.namemust matchHPCEngine.EngineIdin eachHPCServer.json.download_pathmust be writable by RenderAPI and appropriate for your OS.machines.ip_addressandportmust point to running HPCServer instances.users.tokenis the raw token the client sends in thetokenheader.
Create HPCServer.json next to the HPCServer executable:
{
"Port": "5001",
"RenderingEngines": [
{
"EngineId": "blender",
"ExecutablePath": "/usr/bin/blender"
}
]
}Key points:
Portmust match themachines.portentry for this node.RenderingEngines[n].EngineIdmust exactly matchengines.namein the DB.ExecutablePathpoints to the renderer binary on that machine.
Build and run:
cd HPCServer
dotnet build
dotnet runThe server will listen on http://0.0.0.0:<Port> and expose /hpc/* endpoints.
Create RenderAPI.json next to the RenderAPI executable.
For HTTP:
{
"ConnectionString": "Server=localhost;Database=renderonline;User Id=renderonline;Password=yourpassword;",
"Port": "5000",
"Certificate": null
}For HTTPS with PEM certificate files:
{
"ConnectionString": "Server=localhost;Database=renderonline;User Id=renderonline;Password=yourpassword;",
"Port": "5001",
"Certificate": {
"FullchainPemPath": "/etc/ssl/renderonline/fullchain.pem",
"PrivPemPath": "/etc/ssl/renderonline/privkey.pem"
}
}Build and run:
cd RenderAPI
dotnet build
dotnet runRenderAPI will:
- Validate DB connectivity on startup.
- Listen on the configured port (HTTP or HTTPS).
- Start the polling service that assigns queued tasks to machines and monitors completion.
cd RNOClient.Desktop
dotnet build
dotnet runBy default, MainView uses:
_ipAddress = "127.0.0.1"_port = null→ HTTPS tohttps://127.0.0.1
Adjust _ipAddress and _port in RNOClient/Views/MainView.axaml.cs to point to your RenderAPI instance. If you are running RenderAPI over HTTP on port 5000, for example:
- Set
_ipAddressto your API host. - Set
_portto"5000"(client then useshttp://<ip>:<port>).
cd RNOClient
dotnet build
dotnet runThis starts the browser‑targeted app (StartBrowserAppAsync("out")).
Refer to Avalonia Browser documentation for static hosting if you want to deploy it.
- Enter the email and token you seeded in the
userstable. - Click Validate:
- The client calls
GET /renderapi/v1/info. - On success, tasks and user info are loaded and shown.
- The client calls
- Use Upload to enqueue a render:
- Pick a project file (must match the extension in
engines.extension, e.g..blend). - Set
start_frameandend_frame. - Pick an output format (mapped to
output_formatargtype).
- Pick a project file (must match the extension in
- Monitor tasks:
- Green check: finished successfully (download available).
- Busy icon: queued or running.
- Red error icon: failed.
- Use the trash icon to delete a task and its data.
- Use the download icon to save a ZIP archive of the render result.
HPCServer runs on each render node and is responsible for:
- Managing at most one render process at a time.
- Starting CLI render engines with provided arguments.
- Tracking process lifetime, status, and exit code.
- Exposing a simple HTTP API for RenderAPI to start/stop tasks and query status.
HPCServer.json (see Quick Start) defines:
Port: Kestrel listen port (HTTP only).RenderingEngines: array ofHPCEngine:EngineId: identifier used by RenderAPI; must matchengines.namein DB.ExecutablePath: full path to renderer binary (e.g.,/usr/bin/blender).
Base URL: http://<ip>:<port>
Returns machine status and current task information.
Example response:
{
"EngineIds": ["blender"],
"Task": {
"TaskId": 123,
"IsSuccess": false,
"IsRunning": true,
"TotalSeconds": 42
}
}EngineIds: the configured engine identifiers fromHPCServer.json.Task: null if no task has been started since server start.IsRunning: true while the process is active.IsSuccess: last exit code equals 0.
Starts a new render process if none is currently running.
Request body (HPCStartArgs):
{
"EngineId": "blender",
"TaskId": 123,
"Arguments": "blender -b /path/to/file.blend -s 1 -e 100 -a"
}Response (HPCRenderRequestResponse):
{
"IsSuccess": true,
"Message": "Rendering using engine with identifier: blender"
}Error conditions (HTTP 400):
- A render is already in progress.
- Engine ID not configured.
- Invalid arguments.
Stops the currently running render process (if it matches the task ID).
Request body (HPCStopArgs):
{
"TaskId": 123
}Response:
{
"IsSuccess": true,
"Message": "Active render terminated!"
}If no active render is running, it still returns HTTP 200 with a message indicating there was no active render.
HPCServer uses Process.WaitForExitAsync to determine completion and treats exit code 0 as success.
RenderAPI is the central orchestration service that:
- Authenticates users via headers.
- Exposes all public HTTP endpoints (
/renderapi/v1/*). - Manages the render queue and machine inventory in MySQL.
- Assigns tasks to idle HPCServer nodes.
- Polls and updates task states based on HPCServer status.
Each HTTP request from RNOClient (or other clients) must include:
emailheader: matchesusers.email.tokenheader: matchesusers.tokenandis_active = 1.
If missing or invalid, endpoints generally return HTTP 401 or a JSON error response.
Base URL: e.g. https://<host>:<port> (depending on your RenderAPI.json).
Returns user info and last tasks for the authenticated user.
-
Headers:
email,token -
Response (
ApiInfoResponse):{ "User": { "UserId": 1, "FirstName": "Test", "LastName": "User", "Email": "user@example.com", "SubscriptionId": 1, "IsActive": true }, "Tasks": [ { "Task": { "TaskId": 123, "UserId": 1, "QueueTime": "2024-08-27T20:00:00", "StartTime": "2024-08-27T20:01:00", "EndTime": "2024-08-27T20:05:00", "IsRunning": false, "IsSuccess": true, "RenderId": 5, "MachineId": 1 }, "Render": { "RenderId": 5, "FileName": "scene.blend", "FilePath": "", "FileSize": 12345678, "Arguments": "blender -b ...", "EngineId": 1 }, "Engine": { "EngineId": 1, "Name": "blender", "Extension": ".blend", "DownloadPath": "/srv/renderonline/downloads", "RenderArgument": "blender -b ..." } } ] }
Only the last 15 tasks per user are returned, ordered by task_id descending.
Enqueues a new render task.
- Headers:
email,token - Content type:
multipart/form-data - Parts:
request– JSONApiEnqueueRequest:{ "EngineId": "1", "Arguments": [ { "ArgTypeId": "start_frame", "Value": "1" }, { "ArgTypeId": "end_frame", "Value": "100" }, { "ArgTypeId": "output_format", "Value": ".png" } ] }- File – a single project file (e.g.,
.blend).
Validation performed:
- User’s
subscription.queue_limitvs current queued tasks for that user. - Engine existence and
extensionmatching the file extension. - Each argument’s
ArgTypeIdmust exist inargtypes. - Value checked either with:
- Built‑in regex for
argtypes.type(file,path,extension,word,sentence,natural,integer,real), or - Custom
argtypes.regexif provided.
- Built‑in regex for
Argument expansion:
- Starts from
engines.render_argument. - Replaces
$RENDERONLINE:<ArgTypeId>with provided values. - Replaces
$RENDERONLINE:@uploaded_filewith the saved file path.
Persistence:
- Saves uploaded file under
engines.download_path/<user_id>/<queue_time_ticks>/. - Inserts row in
renders. - Inserts row in
taskswithqueue_time,is_running = 0,is_success = 0. - Inserts row in
queuewithtask_id.
Response (ApiEnqueueResponse):
{
"IsAdded": true,
"ErrorMessage": "Task successfully enqueued."
}On validation errors, returns appropriate HTTP 4xx and IsAdded = false with details.
Removes a queued task from the queue (and optionally stops it on the machine).
-
Headers:
email,token -
Content type:
application/json -
Body (
ApiDequeueRequest):{ "TaskId": 123 }
Flow:
- Validate task ownership (
tasks.user_id == current user). - Confirm the task is present in
queue. - Delete the row from
queue. - Load the full task (joins
tasks,renders,engines). - If
tasks.machine_idis not null:- Lookup machine IP and port in
machines. - Call
POST /hpc/stopon that machine with matchingTaskId.
- Lookup machine IP and port in
Response (ApiDequeueResponse):
{
"IsRemoved": true,
"ErrorMessage": "Task successfully dequeued."
}Downloads a ZIP archive of the render output directory.
-
Headers:
email,token -
Content type:
application/json -
Body (
ApiDownloadRequest):{ "TaskId": 123 }
Flow:
- Validate task ownership.
- Retrieve
renders.file_pathfor that task. - Compute parent directory of
file_path. - Create a temporary ZIP using
ZipFile.CreateFromDirectory. - Stream the ZIP with headers:
Content-Type: application/zipContent-Disposition: attachment; filename=<guid>.zip
- Attempt to delete the temporary ZIP after sending.
On success, binary ZIP is returned. On failure, JSON ApiDownloadResponse is returned with DownloadProvided = false.
Fully deletes a task and its associated render.
-
Headers:
email,token -
Content type:
application/json -
Body (
ApiDeleteRequest):{ "TaskId": 123 }
Flow:
- Validate task belongs to current user.
- Resolve
render_idandfile_pathvia join ontasksandrenders. - Remove from
queue(if present). - Delete the directory that contains
file_path(with retries). - Delete the row from
tasks. - Delete the row from
renders.
Response (ApiDeleteResponse):
{
"IsDeleted": true,
"ErrorMessage": "Task and associated render successfully deleted."
}StartPollingService() runs an infinite background loop:
-
Every 15 seconds:
-
Query all tasks that:
- Are in
queue(via join). - Have
is_success = 0.
- Are in
-
For each
ApiTaskInfo:- If
task.MachineId == nullandtask.IsRunning == false:- Call
AssignTaskToMachine(task):- Enumerates machines from
machines. - For each, calls
GET http://<ip>:<port>/hpc/status. - If machine idle (no running task), calls
POST /hpc/startwith:EngineId=engine.Name(matches HPCServer engine ID).Arguments=render.Arguments.
- On success, calls
UpdateTaskStartDetails(taskId, machineId)(setsstart_time,is_running,machine_id).
- Enumerates machines from
- Call
- Else:
- Call
CheckTaskStatusOnMachine(task.Task):- Finds machine by
task.MachineId. - Calls
/hpc/status. - If
status.Taskpresent:- If not running and success:
CompleteTask(taskId):- Sets
end_time,is_running = 0,is_success = 1, removes fromqueue.
- Sets
- If not running and failure:
HandleTaskFailure(taskId, machineId):- Sets
start_time/end_timeto now,is_running = 0,is_success = 0, removes fromqueue.
- Sets
- If not running and success:
- Finds machine by
- Call
- If
-
This design keeps HPCServer mostly stateless, with RenderAPI and the database being the source of truth.
RNOClient is a thin UI client for RenderAPI:
- Shows authenticated user info and last tasks.
- Visualizes states: queued, running, success, failure.
- Provides an upload dialog for enqueueing tasks.
- Wraps delete and download flows with confirmation and OS file pickers.
MainView.axaml.cs- Handles:
- Login (email/token headers).
GET /renderapi/v1/info.- Delete, download, and enqueue triggers.
- Handles:
UploadView.axaml.cs- Provides file picker and argument fields (
start_frame,end_frame,output_format).
- Provides file picker and argument fields (
TaskView.axaml.cs- Visual representation of each
ApiTaskInfowith icons and action buttons.
- Visual representation of each
ITaskListener/IUIInfluencer- Interfaces for wiring views to actions and navigation.
- User enters email and token, presses Validate.
RenderAPIInfoRequestloads tasks and shows them inTasksPanel.- Clicking Upload shows
UploadView:Browseselects project file.- User sets frame range and format.
- Enqueue sends
ApiEnqueueRequest+ file via multipart/form‑data.
- After enqueue or delete,
RenderAPIInfoRequestrefreshes the task list. - Download uses
SaveFileUsingStorageProviderto store ZIP fromRenderAPI.
The database scripts in Database/ (renderonline_*.sql) create the following schema:
usersuser_id,first_name,last_name,emailsubscription_id(FK conceptually tosubscriptions)is_activetoken(used verbatim as API token)
subscriptionssubscription_id,name,queue_limit- Controls maximum number of queued tasks per user.
enginesengine_id,name,extension,download_path,render_argumentnameis referenced by HPCServer (EngineId).extensionis used to validate upload file type.download_pathis the root directory for per‑task folders.render_argumentis a template string with$RENDERONLINE:...placeholders.
argtypesargtype_id,type,regex- Defines allowed argument types for templating:
- Supported
typevalues:file,path,extension,word,sentence,natural,integer,real. - If
regexis null, RenderAPI uses built‑in regex for the giventype.
- Supported
machinesmachine_id,ip_address,port- Describes available HPCServer nodes;
ip_addressandportmust match node configuration.
rendersrender_id,file_name,file_path,file_size,arguments,engine_id- Stores uploaded file metadata and fully expanded render arguments.
taskstask_id,user_id,queue_time,start_time,end_timeis_running,is_successrender_id,machine_id- Represents individual render jobs.
queuequeue_id,task_id- Presence in this table means the task is queued; scheduler logic removes entries on completion, failure, or dequeue.
The SQL scripts do not declare foreign keys but the logical relationships are as described above.
- Projects are plain .NET applications:
- HPCServer and RenderAPI use ASP.NET Core minimal hosting with manual route mapping (
MapGet,MapPost). - RNOClient uses Avalonia for the cross‑platform UI.
- HPCServer and RenderAPI use ASP.NET Core minimal hosting with manual route mapping (
- Logging is currently console‑based.
- JSON serialization uses Newtonsoft.Json (
JsonConvert). - Render execution is delegated to external processes via
System.Diagnostics.Process.
To build all components:
dotnet build HPCServer
dotnet build RenderAPI
dotnet build RNOClient.Desktop
dotnet build RNOClient- More advanced scheduling:
- Priorities, retries, backoff, and machine load awareness.
- Multi‑engine and multi‑output support per task.
- Per‑task logs and stdout/stderr streaming.
- Web (HTML) client alternative to the Avalonia application.
- Stronger authentication and user/role management APIs.
- Optional metrics and monitoring endpoints.
Contributions are welcome.
- Open issues for bugs or feature requests.
- Submit PRs focused on a single area (HPCServer, RenderAPI, RNOClient, or Database).
- Please keep code style consistent with the existing C#.
See LICENSE.

