Website: add Cycles/OPTIX/GPU job type
Add the latest version of the Blender Studio custom job type to the website, as it does a few things others may find interesting.
This commit is contained in:
parent
44ffb09d7d
commit
80f7ae99b9
@ -4,14 +4,25 @@ weight: 30
|
|||||||
---
|
---
|
||||||
|
|
||||||
This section contains third-party job types for Flamenco. These have been
|
This section contains third-party job types for Flamenco. These have been
|
||||||
submitted by the community. If you wish to contribute, consider joining the
|
submitted by the community. If you wish to contribute your custom job type,
|
||||||
[Blender Chat channel][flamencochannel] and chime-in there.
|
either
|
||||||
|
|
||||||
|
- join the [#flamenco Blender Chat channel][flamencochannel] and poke `@dr.sybren`, or
|
||||||
|
- write an [issue in the tracker][tracker] with your proposal.
|
||||||
|
|
||||||
|
|
||||||
## How can I create my own Job Type?
|
## How can I create my own Job Type?
|
||||||
|
|
||||||
This is described [Job Types][jobtypes]. It is recommended to use the
|
This is described [Job Types][jobtypes]. It is recommended to use the
|
||||||
[built-in scripts][built-in-scripts] as examples and adjust them from there.
|
[built-in scripts][built-in-scripts] as examples and adjust them from there.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Each job type consists of a `.js` file. After downloading, place those in the
|
||||||
|
`scripts` directory next to the Flamenco Manager executable. Create the
|
||||||
|
directory if necessary. Then restart Flamenco Manager and in Blender press the
|
||||||
|
"Refresh from Manager" button.
|
||||||
|
|
||||||
## Third-Party Job Types
|
## Third-Party Job Types
|
||||||
|
|
||||||
{{< flamenco/toc-children >}}
|
{{< flamenco/toc-children >}}
|
||||||
@ -19,3 +30,4 @@ This is described [Job Types][jobtypes]. It is recommended to use the
|
|||||||
[jobtypes]: {{< ref "usage/job-types" >}}
|
[jobtypes]: {{< ref "usage/job-types" >}}
|
||||||
[built-in-scripts]: https://projects.blender.org/studio/flamenco/src/branch/main/internal/manager/job_compilers/scripts
|
[built-in-scripts]: https://projects.blender.org/studio/flamenco/src/branch/main/internal/manager/job_compilers/scripts
|
||||||
[flamencochannel]: https://blender.chat/channel/flamenco
|
[flamencochannel]: https://blender.chat/channel/flamenco
|
||||||
|
[tracker]: https://projects.blender.org/studio/flamenco/issues
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
@ -0,0 +1,317 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
const JOB_TYPE = {
|
||||||
|
label: 'Cycles OPTIX GPU',
|
||||||
|
description:
|
||||||
|
'OPTIX GPU rendering + extra checkboxes for some experimental features + extra CLI args for Blender',
|
||||||
|
settings: [
|
||||||
|
// Settings for artists to determine:
|
||||||
|
{
|
||||||
|
key: 'frames',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
eval: "f'{C.scene.frame_start}-{C.scene.frame_end}'",
|
||||||
|
evalInfo: {
|
||||||
|
showLinkButton: true,
|
||||||
|
description: 'Scene frame range',
|
||||||
|
},
|
||||||
|
description: "Frame range to render. Examples: '47', '1-30', '3, 5-10, 47-327'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'chunk_size',
|
||||||
|
type: 'int32',
|
||||||
|
default: 1,
|
||||||
|
description: 'Number of frames to render in one Blender render task',
|
||||||
|
visible: 'submission',
|
||||||
|
},
|
||||||
|
|
||||||
|
// render_output_root + add_path_components determine the value of render_output_path.
|
||||||
|
{
|
||||||
|
key: 'render_output_root',
|
||||||
|
type: 'string',
|
||||||
|
subtype: 'dir_path',
|
||||||
|
required: true,
|
||||||
|
visible: 'submission',
|
||||||
|
description:
|
||||||
|
'Base directory of where render output is stored. Will have some job-specific parts appended to it',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'add_path_components',
|
||||||
|
type: 'int32',
|
||||||
|
required: true,
|
||||||
|
default: 0,
|
||||||
|
propargs: { min: 0, max: 32 },
|
||||||
|
visible: 'submission',
|
||||||
|
description:
|
||||||
|
'Number of path components of the current blend file to use in the render output path',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'render_output_path',
|
||||||
|
type: 'string',
|
||||||
|
subtype: 'file_path',
|
||||||
|
editable: false,
|
||||||
|
eval: "str(Path(abspath(settings.render_output_root), last_n_dir_parts(settings.add_path_components), jobname, '{timestamp}', '######'))",
|
||||||
|
description: 'Final file path of where render output will be saved',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
key: 'experimental_gp3',
|
||||||
|
label: 'Experimental: GPv3',
|
||||||
|
description: 'Experimental Flag: Grease Pencil 3',
|
||||||
|
type: 'bool',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'experimental_new_anim',
|
||||||
|
label: 'Experimental: Baklava',
|
||||||
|
description: 'Experimental Flag: New Animation Data-block',
|
||||||
|
type: 'bool',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Extra CLI arguments for Blender, for debugging purposes.
|
||||||
|
{
|
||||||
|
key: 'blender_args_before',
|
||||||
|
label: 'Blender CLI args: Before',
|
||||||
|
description: 'CLI arguments for Blender, placed before the .blend filename',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'blender_args_after',
|
||||||
|
label: 'After',
|
||||||
|
description: 'CLI arguments for Blender, placed after the .blend filename',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Automatically evaluated settings:
|
||||||
|
{
|
||||||
|
key: 'blendfile',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
description: 'Path of the Blend file to render',
|
||||||
|
visible: 'web',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'fps',
|
||||||
|
type: 'float',
|
||||||
|
eval: 'C.scene.render.fps / C.scene.render.fps_base',
|
||||||
|
visible: 'hidden',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'format',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
eval: 'C.scene.render.image_settings.file_format',
|
||||||
|
visible: 'web',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'image_file_extension',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
eval: 'C.scene.render.file_extension',
|
||||||
|
visible: 'hidden',
|
||||||
|
description: 'File extension used when rendering images',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'has_previews',
|
||||||
|
type: 'bool',
|
||||||
|
required: false,
|
||||||
|
eval: 'C.scene.render.image_settings.use_preview',
|
||||||
|
visible: 'hidden',
|
||||||
|
description: 'Whether Blender will render preview images.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set of scene.render.image_settings.file_format values that produce
|
||||||
|
// files which FFmpeg is known not to handle as input.
|
||||||
|
const ffmpegIncompatibleImageFormats = new Set([
|
||||||
|
'EXR',
|
||||||
|
'MULTILAYER', // Old CLI-style format indicators
|
||||||
|
'OPEN_EXR',
|
||||||
|
'OPEN_EXR_MULTILAYER', // DNA values for these formats.
|
||||||
|
]);
|
||||||
|
|
||||||
|
// File formats that would cause rendering to video.
|
||||||
|
// This is not supported by this job type.
|
||||||
|
const videoFormats = ['FFMPEG', 'AVI_RAW', 'AVI_JPEG'];
|
||||||
|
|
||||||
|
function compileJob(job) {
|
||||||
|
print('Blender Render job submitted');
|
||||||
|
print('job: ', job);
|
||||||
|
|
||||||
|
const settings = job.settings;
|
||||||
|
if (videoFormats.indexOf(settings.format) >= 0) {
|
||||||
|
throw `This job type only renders images, and not "${settings.format}"`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderOutput = renderOutputPath(job);
|
||||||
|
|
||||||
|
// Make sure that when the job is investigated later, it shows the
|
||||||
|
// actually-used render output:
|
||||||
|
settings.render_output_path = renderOutput;
|
||||||
|
|
||||||
|
const renderDir = path.dirname(renderOutput);
|
||||||
|
const renderTasks = authorRenderTasks(settings, renderDir, renderOutput);
|
||||||
|
const videoTask = authorCreateVideoTask(settings, renderDir);
|
||||||
|
|
||||||
|
for (const rt of renderTasks) {
|
||||||
|
job.addTask(rt);
|
||||||
|
}
|
||||||
|
if (videoTask) {
|
||||||
|
// If there is a video task, all other tasks have to be done first.
|
||||||
|
for (const rt of renderTasks) {
|
||||||
|
videoTask.addDependency(rt);
|
||||||
|
}
|
||||||
|
job.addTask(videoTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanupJobSettings(job.settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do field replacement on the render output path.
|
||||||
|
function renderOutputPath(job) {
|
||||||
|
let path = job.settings.render_output_path;
|
||||||
|
if (!path) {
|
||||||
|
throw 'no render_output_path setting!';
|
||||||
|
}
|
||||||
|
return path.replace(/{([^}]+)}/g, (match, group0) => {
|
||||||
|
switch (group0) {
|
||||||
|
case 'timestamp':
|
||||||
|
return formatTimestampLocal(job.created);
|
||||||
|
default:
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const enable_all_optix = `
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
cycles_prefs = bpy.context.preferences.addons['cycles'].preferences
|
||||||
|
cycles_prefs.compute_device_type = 'OPTIX'
|
||||||
|
for dev in cycles_prefs.get_devices_for_type('OPTIX'):
|
||||||
|
dev.use = (dev.type != 'CPU')
|
||||||
|
`;
|
||||||
|
|
||||||
|
const enable_experimental_common = `
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
exp_prefs = bpy.context.preferences.experimental
|
||||||
|
`;
|
||||||
|
|
||||||
|
function authorRenderTasks(settings, renderDir, renderOutput) {
|
||||||
|
print('authorRenderTasks(', renderDir, renderOutput, ')');
|
||||||
|
|
||||||
|
// Extra arguments for Blender.
|
||||||
|
const blender_args_before = shellSplit(settings.blender_args_before);
|
||||||
|
const blender_args_after = shellSplit(settings.blender_args_after);
|
||||||
|
|
||||||
|
// More arguments for Blender, which will be the same for each task.
|
||||||
|
const task_invariant_args = [
|
||||||
|
'--python-expr',
|
||||||
|
enable_all_optix,
|
||||||
|
'--python-expr',
|
||||||
|
"import bpy; bpy.context.scene.cycles.device = 'GPU'",
|
||||||
|
'--render-output',
|
||||||
|
path.join(renderDir, path.basename(renderOutput)),
|
||||||
|
'--render-format',
|
||||||
|
settings.format,
|
||||||
|
].concat(blender_args_after);
|
||||||
|
|
||||||
|
// Add any experimental flags.
|
||||||
|
{
|
||||||
|
let py_code_to_join = [enable_experimental_common];
|
||||||
|
if (settings.experimental_gp3) {
|
||||||
|
py_code_to_join.push('exp_prefs.use_grease_pencil_version3 = True');
|
||||||
|
}
|
||||||
|
if (settings.experimental_new_anim) {
|
||||||
|
py_code_to_join.push('exp_prefs.use_animation_baklava = True');
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's not just the common code, at least one flag was enabled.
|
||||||
|
if (py_code_to_join.length > 1) {
|
||||||
|
task_invariant_args.push('--python-expr');
|
||||||
|
task_invariant_args.push(py_code_to_join.join('\n'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct a task for each chunk.
|
||||||
|
let renderTasks = [];
|
||||||
|
let chunks = frameChunker(settings.frames, settings.chunk_size);
|
||||||
|
for (let chunk of chunks) {
|
||||||
|
const task = author.Task(`render-${chunk}`, 'blender');
|
||||||
|
const command = author.Command('blender-render', {
|
||||||
|
exe: '{blender}',
|
||||||
|
exeArgs: '{blenderArgs}',
|
||||||
|
argsBefore: blender_args_before,
|
||||||
|
blendfile: settings.blendfile,
|
||||||
|
args: task_invariant_args.concat([
|
||||||
|
'--render-frame',
|
||||||
|
chunk.replaceAll('-', '..'), // Convert to Blender frame range notation.
|
||||||
|
]),
|
||||||
|
});
|
||||||
|
task.addCommand(command);
|
||||||
|
renderTasks.push(task);
|
||||||
|
}
|
||||||
|
return renderTasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
function authorCreateVideoTask(settings, renderDir) {
|
||||||
|
const needsPreviews = ffmpegIncompatibleImageFormats.has(settings.format);
|
||||||
|
if (needsPreviews && !settings.has_previews) {
|
||||||
|
print('Not authoring video task, FFmpeg-incompatible render output');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!settings.fps) {
|
||||||
|
print('Not authoring video task, no FPS known:', settings);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stem = path.stem(settings.blendfile).replace('.flamenco', '');
|
||||||
|
const outfile = path.join(renderDir, `${stem}-${settings.frames}.mp4`);
|
||||||
|
const outfileExt = needsPreviews ? '.jpg' : settings.image_file_extension;
|
||||||
|
|
||||||
|
const task = author.Task('preview-video', 'ffmpeg');
|
||||||
|
const command = author.Command('frames-to-video', {
|
||||||
|
exe: 'ffmpeg',
|
||||||
|
fps: settings.fps,
|
||||||
|
inputGlob: path.join(renderDir, `*${outfileExt}`),
|
||||||
|
outputFile: outfile,
|
||||||
|
args: [
|
||||||
|
'-c:v',
|
||||||
|
'h264',
|
||||||
|
'-crf',
|
||||||
|
'20',
|
||||||
|
'-g',
|
||||||
|
'18',
|
||||||
|
'-vf',
|
||||||
|
'pad=ceil(iw/2)*2:ceil(ih/2)*2',
|
||||||
|
'-pix_fmt',
|
||||||
|
'yuv420p',
|
||||||
|
'-r',
|
||||||
|
settings.fps,
|
||||||
|
'-y', // Be sure to always pass either "-n" or "-y".
|
||||||
|
],
|
||||||
|
});
|
||||||
|
task.addCommand(command);
|
||||||
|
|
||||||
|
print(`Creating output video for ${settings.format}`);
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up empty job settings so that they're no longer shown in the web UI.
|
||||||
|
function cleanupJobSettings(settings) {
|
||||||
|
const settings_to_check = [
|
||||||
|
'blender_args_before',
|
||||||
|
'blender_args_after',
|
||||||
|
'experimental_gp3',
|
||||||
|
'experimental_new_anim',
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let setting_name of settings_to_check) {
|
||||||
|
if (!settings[setting_name]) delete settings[setting_name];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
title: Cycles/OPTIX + Experimental
|
||||||
|
weight: 20
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- name: screenshot
|
||||||
|
src: cycles-optix-gpu.webp
|
||||||
|
title: Screenshot of the Flamenco job submission panel in Blender
|
||||||
|
---
|
||||||
|
|
||||||
|
{{< flamenco/thirdPartyCompatibility blender="v4.2-alpha+" flamenco="v3.5+" >}}
|
||||||
|
Documented and maintained by [Sybren Stüvel][author].
|
||||||
|
Please report any issues at [Flamenco's tracker][tracker].
|
||||||
|
|
||||||
|
[author]: https://projects.blender.org/dr.sybren
|
||||||
|
[tracker]: https://projects.blender.org/studio/flamenco/issues
|
||||||
|
{{< /flamenco/thirdPartyCompatibility >}}
|
||||||
|
|
||||||
|
This job type is the most-used one at [Blender Studio](https://studio.blender.org/). It includes a few features:
|
||||||
|
|
||||||
|
- Always enable GPU rendering with OPTIX.
|
||||||
|
- Checkboxes to enable specific experimental flags.
|
||||||
|
- Extra input fields for arbitrary commandline arguments for Blender.
|
||||||
|
|
||||||
|
To use, download [cycles_optix_gpu.js](cycles_optix_gpu.js) and place it in the
|
||||||
|
`scripts` directory next to the Flamenco Manager executable. Create the
|
||||||
|
directory if necessary. Then restart Flamenco Manager and in Blender press the
|
||||||
|
"Refresh from Manager" button.
|
||||||
|
|
||||||
|
<style>
|
||||||
|
figure {
|
||||||
|
width: 30em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
{{< img name="screenshot" size="medium" >}}
|
@ -224,13 +224,13 @@ table tbody td {
|
|||||||
margin: 0.6ex;
|
margin: 0.6ex;
|
||||||
}
|
}
|
||||||
.compatibility-box dl {
|
.compatibility-box dl {
|
||||||
flex-basis: 20%;
|
flex-basis: 30%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: row wrap;
|
flex-flow: row wrap;
|
||||||
}
|
}
|
||||||
.compatibility-box dl dt {
|
.compatibility-box dl dt {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
flex-basis: 66%;
|
flex-basis: 55%;
|
||||||
padding: 0.2em 0.4em;
|
padding: 0.2em 0.4em;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
@ -238,7 +238,7 @@ table tbody td {
|
|||||||
content: ":";
|
content: ":";
|
||||||
}
|
}
|
||||||
.compatibility-box dl dd {
|
.compatibility-box dl dd {
|
||||||
flex-basis: 33%;
|
flex-basis: 45%;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0.2em 0.4em;
|
padding: 0.2em 0.4em;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user