Implement GPU backend detection for Blender compatibility
- Added functionality to detect GPU backends (HIP and NVIDIA) during runner registration, enhancing compatibility for Blender versions below 4.x. - Introduced a new method, DetectAndStoreGPUBackends, to download the latest Blender and run a detection script, storing the results for future rendering decisions. - Updated rendering logic to force CPU rendering when HIP is detected on systems with Blender < 4.x, ensuring stability and compatibility. - Enhanced the Context structure to include flags for GPU detection status, improving error handling and rendering decisions based on GPU availability.
This commit is contained in:
@@ -161,6 +161,9 @@ func runRunner(cmd *cobra.Command, args []string) {
|
|||||||
runnerID, err = r.Register(apiKey)
|
runnerID, err = r.Register(apiKey)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
logger.Infof("Registered runner with ID: %d", runnerID)
|
logger.Infof("Registered runner with ID: %d", runnerID)
|
||||||
|
// Download latest Blender and detect HIP vs NVIDIA so we only force CPU for Blender < 4.x when using HIP
|
||||||
|
logger.Info("Detecting GPU backends (HIP/NVIDIA) for Blender < 4.x policy...")
|
||||||
|
r.DetectAndStoreGPUBackends()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
authpkg "jiggablend/internal/auth"
|
authpkg "jiggablend/internal/auth"
|
||||||
|
"jiggablend/internal/runner/blender"
|
||||||
"jiggablend/pkg/executils"
|
"jiggablend/pkg/executils"
|
||||||
"jiggablend/pkg/scripts"
|
"jiggablend/pkg/scripts"
|
||||||
"jiggablend/pkg/types"
|
"jiggablend/pkg/types"
|
||||||
@@ -2036,12 +2037,13 @@ func (s *Manager) runBlenderMetadataExtraction(blendFile, workDir, blenderVersio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute Blender using executils
|
// Execute Blender using executils (set LD_LIBRARY_PATH for tarball installs)
|
||||||
|
runEnv := blender.TarballEnv(blenderBinary, os.Environ())
|
||||||
result, err := executils.RunCommand(
|
result, err := executils.RunCommand(
|
||||||
blenderBinary,
|
blenderBinary,
|
||||||
[]string{"-b", blendFileRel, "--python", "extract_metadata.py"},
|
[]string{"-b", blendFileRel, "--python", "extract_metadata.py"},
|
||||||
workDir,
|
workDir,
|
||||||
nil, // inherit environment
|
runEnv,
|
||||||
0, // no task ID for metadata extraction
|
0, // no task ID for metadata extraction
|
||||||
nil, // no process tracker needed
|
nil, // no process tracker needed
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -442,3 +442,32 @@ func (m *ManagerClient) DownloadBlender(version string) (io.ReadCloser, error) {
|
|||||||
|
|
||||||
return resp.Body, nil
|
return resp.Body, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// blenderVersionsResponse is the response from GET /api/blender/versions.
|
||||||
|
type blenderVersionsResponse struct {
|
||||||
|
Versions []struct {
|
||||||
|
Full string `json:"full"`
|
||||||
|
} `json:"versions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLatestBlenderVersion returns the latest Blender version string (e.g. "4.2.3") from the manager.
|
||||||
|
// Uses the flat versions list which is newest-first.
|
||||||
|
func (m *ManagerClient) GetLatestBlenderVersion() (string, error) {
|
||||||
|
resp, err := m.Request("GET", "/api/blender/versions", nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to fetch blender versions: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
return "", fmt.Errorf("blender versions returned status %d: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
var out blenderVersionsResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to decode blender versions: %w", err)
|
||||||
|
}
|
||||||
|
if len(out.Versions) == 0 {
|
||||||
|
return "", fmt.Errorf("no blender versions available")
|
||||||
|
}
|
||||||
|
return out.Versions[0].Full, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"jiggablend/internal/runner/api"
|
"jiggablend/internal/runner/api"
|
||||||
"jiggablend/internal/runner/workspace"
|
"jiggablend/internal/runner/workspace"
|
||||||
@@ -85,3 +86,42 @@ func (m *Manager) GetBinaryForJob(version string) (string, error) {
|
|||||||
return m.GetBinaryPath(version)
|
return m.GetBinaryPath(version)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TarballEnv returns a copy of baseEnv with LD_LIBRARY_PATH set so that a
|
||||||
|
// tarball Blender installation can find its bundled libs (e.g. lib/python3.x).
|
||||||
|
// If blenderBinary is the system "blender" or has no path component, baseEnv is
|
||||||
|
// returned unchanged.
|
||||||
|
func TarballEnv(blenderBinary string, baseEnv []string) []string {
|
||||||
|
if blenderBinary == "" || blenderBinary == "blender" {
|
||||||
|
return baseEnv
|
||||||
|
}
|
||||||
|
if !strings.Contains(blenderBinary, string(os.PathSeparator)) {
|
||||||
|
return baseEnv
|
||||||
|
}
|
||||||
|
blenderDir := filepath.Dir(blenderBinary)
|
||||||
|
libDir := filepath.Join(blenderDir, "lib")
|
||||||
|
ldLib := libDir
|
||||||
|
for _, e := range baseEnv {
|
||||||
|
if strings.HasPrefix(e, "LD_LIBRARY_PATH=") {
|
||||||
|
existing := strings.TrimPrefix(e, "LD_LIBRARY_PATH=")
|
||||||
|
if existing != "" {
|
||||||
|
ldLib = libDir + ":" + existing
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out := make([]string, 0, len(baseEnv)+1)
|
||||||
|
done := false
|
||||||
|
for _, e := range baseEnv {
|
||||||
|
if strings.HasPrefix(e, "LD_LIBRARY_PATH=") {
|
||||||
|
out = append(out, "LD_LIBRARY_PATH="+ldLib)
|
||||||
|
done = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = append(out, e)
|
||||||
|
}
|
||||||
|
if !done {
|
||||||
|
out = append(out, "LD_LIBRARY_PATH="+ldLib)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
45
internal/runner/blender/detect.go
Normal file
45
internal/runner/blender/detect.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
// Package blender: GPU backend detection for HIP vs NVIDIA.
|
||||||
|
package blender
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"jiggablend/pkg/scripts"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DetectGPUBackends runs a minimal Blender script to detect whether HIP (AMD) and/or
|
||||||
|
// NVIDIA (CUDA/OptiX) devices are available. Use this to decide whether to force CPU
|
||||||
|
// for Blender < 4.x (only force when HIP is present, since HIP has no official support pre-4).
|
||||||
|
func DetectGPUBackends(blenderBinary, scriptDir string) (hasHIP, hasNVIDIA bool, err error) {
|
||||||
|
scriptPath := filepath.Join(scriptDir, "detect_gpu_backends.py")
|
||||||
|
if err := os.WriteFile(scriptPath, []byte(scripts.DetectGPUBackends), 0644); err != nil {
|
||||||
|
return false, false, fmt.Errorf("write detection script: %w", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(scriptPath)
|
||||||
|
|
||||||
|
env := TarballEnv(blenderBinary, os.Environ())
|
||||||
|
cmd := exec.Command(blenderBinary, "-b", "--python", scriptPath)
|
||||||
|
cmd.Env = env
|
||||||
|
cmd.Dir = scriptDir
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return false, false, fmt.Errorf("run blender detection: %w (output: %s)", err, string(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(strings.NewReader(string(out)))
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
switch line {
|
||||||
|
case "HAS_HIP":
|
||||||
|
hasHIP = true
|
||||||
|
case "HAS_NVIDIA":
|
||||||
|
hasNVIDIA = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hasHIP, hasNVIDIA, scanner.Err()
|
||||||
|
}
|
||||||
@@ -45,6 +45,15 @@ type Runner struct {
|
|||||||
// when true, the runner forces CPU rendering for all subsequent jobs.
|
// when true, the runner forces CPU rendering for all subsequent jobs.
|
||||||
gpuLockedOut bool
|
gpuLockedOut bool
|
||||||
gpuLockedOutMu sync.RWMutex
|
gpuLockedOutMu sync.RWMutex
|
||||||
|
|
||||||
|
// hasHIP/hasNVIDIA are set at startup by running latest Blender to detect GPU backends.
|
||||||
|
// Used to force CPU only for Blender < 4.x when HIP is present (no official HIP support pre-4).
|
||||||
|
// gpuDetectionFailed is true when detection could not run; we then force CPU for all versions (we could not determine HIP vs NVIDIA).
|
||||||
|
gpuBackendMu sync.RWMutex
|
||||||
|
hasHIP bool
|
||||||
|
hasNVIDIA bool
|
||||||
|
gpuBackendProbed bool
|
||||||
|
gpuDetectionFailed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new runner.
|
// New creates a new runner.
|
||||||
@@ -124,6 +133,58 @@ func (r *Runner) Register(apiKey string) (int64, error) {
|
|||||||
return id, nil
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DetectAndStoreGPUBackends downloads the latest Blender from the manager (if needed),
|
||||||
|
// runs a detection script to see if HIP (AMD) and/or NVIDIA devices are available,
|
||||||
|
// and stores the result. Call after Register. Used so we only force CPU for Blender < 4.x
|
||||||
|
// when the runner has HIP (no official HIP support pre-4); NVIDIA is allowed.
|
||||||
|
func (r *Runner) DetectAndStoreGPUBackends() {
|
||||||
|
r.gpuBackendMu.Lock()
|
||||||
|
defer r.gpuBackendMu.Unlock()
|
||||||
|
if r.gpuBackendProbed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
latestVer, err := r.manager.GetLatestBlenderVersion()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("GPU backend detection failed (could not get latest Blender version: %v). All jobs will use CPU because we could not determine HIP vs NVIDIA.", err)
|
||||||
|
r.gpuBackendProbed = true
|
||||||
|
r.gpuDetectionFailed = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
binaryPath, err := r.blender.GetBinaryPath(latestVer)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("GPU backend detection failed (could not get Blender binary: %v). All jobs will use CPU because we could not determine HIP vs NVIDIA.", err)
|
||||||
|
r.gpuBackendProbed = true
|
||||||
|
r.gpuDetectionFailed = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hasHIP, hasNVIDIA, err := blender.DetectGPUBackends(binaryPath, r.workspace.BaseDir())
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("GPU backend detection failed (script error: %v). All jobs will use CPU because we could not determine HIP vs NVIDIA.", err)
|
||||||
|
r.gpuBackendProbed = true
|
||||||
|
r.gpuDetectionFailed = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.hasHIP = hasHIP
|
||||||
|
r.hasNVIDIA = hasNVIDIA
|
||||||
|
r.gpuBackendProbed = true
|
||||||
|
r.gpuDetectionFailed = false
|
||||||
|
log.Printf("GPU backend detection: HIP=%v NVIDIA=%v (Blender < 4.x will force CPU only when HIP is present)", hasHIP, hasNVIDIA)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasHIP returns whether the runner detected HIP (AMD) devices. Used to force CPU for Blender < 4.x only when HIP is present.
|
||||||
|
func (r *Runner) HasHIP() bool {
|
||||||
|
r.gpuBackendMu.RLock()
|
||||||
|
defer r.gpuBackendMu.RUnlock()
|
||||||
|
return r.hasHIP
|
||||||
|
}
|
||||||
|
|
||||||
|
// GPUDetectionFailed returns true when startup GPU backend detection could not run or failed. When true, all jobs use CPU because we could not determine HIP vs NVIDIA.
|
||||||
|
func (r *Runner) GPUDetectionFailed() bool {
|
||||||
|
r.gpuBackendMu.RLock()
|
||||||
|
defer r.gpuBackendMu.RUnlock()
|
||||||
|
return r.gpuDetectionFailed
|
||||||
|
}
|
||||||
|
|
||||||
// Start starts the job polling loop.
|
// Start starts the job polling loop.
|
||||||
func (r *Runner) Start(pollInterval time.Duration) {
|
func (r *Runner) Start(pollInterval time.Duration) {
|
||||||
log.Printf("Starting job polling loop (interval: %v)", pollInterval)
|
log.Printf("Starting job polling loop (interval: %v)", pollInterval)
|
||||||
@@ -244,6 +305,8 @@ func (r *Runner) executeJob(job *api.NextJobResponse) (err error) {
|
|||||||
r.encoder,
|
r.encoder,
|
||||||
r.processes,
|
r.processes,
|
||||||
r.IsGPULockedOut(),
|
r.IsGPULockedOut(),
|
||||||
|
r.HasHIP(),
|
||||||
|
r.GPUDetectionFailed(),
|
||||||
func() { r.SetGPULockedOut(true) },
|
func() { r.SetGPULockedOut(true) },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import (
|
|||||||
"jiggablend/pkg/executils"
|
"jiggablend/pkg/executils"
|
||||||
"jiggablend/pkg/types"
|
"jiggablend/pkg/types"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -41,6 +43,10 @@ type Context struct {
|
|||||||
|
|
||||||
// GPULockedOut is set when the runner has detected a GPU error (e.g. HIP) and disables GPU for all jobs.
|
// GPULockedOut is set when the runner has detected a GPU error (e.g. HIP) and disables GPU for all jobs.
|
||||||
GPULockedOut bool
|
GPULockedOut bool
|
||||||
|
// HasHIP is true when the runner detected HIP (AMD) devices at startup. Used to force CPU for Blender < 4.x only when HIP is present.
|
||||||
|
HasHIP bool
|
||||||
|
// GPUDetectionFailed is true when startup GPU backend detection could not run; we force CPU for all versions (could not determine HIP vs NVIDIA).
|
||||||
|
GPUDetectionFailed bool
|
||||||
// OnGPUError is called when a GPU error line is seen in render logs; typically sets runner GPU lockout.
|
// OnGPUError is called when a GPU error line is seen in render logs; typically sets runner GPU lockout.
|
||||||
OnGPUError func()
|
OnGPUError func()
|
||||||
}
|
}
|
||||||
@@ -49,7 +55,7 @@ type Context struct {
|
|||||||
var ErrJobCancelled = errors.New("job cancelled")
|
var ErrJobCancelled = errors.New("job cancelled")
|
||||||
|
|
||||||
// NewContext creates a new task context. frameEnd should be >= frame; if 0 or less than frame, it is treated as single-frame (frameEnd = frame).
|
// NewContext creates a new task context. frameEnd should be >= frame; if 0 or less than frame, it is treated as single-frame (frameEnd = frame).
|
||||||
// gpuLockedOut is the runner's current GPU lockout state; onGPUError is called when a GPU error is detected in logs (may be nil).
|
// gpuLockedOut is the runner's current GPU lockout state; hasHIP means the runner has HIP (AMD) devices (force CPU for Blender < 4.x only when true); gpuDetectionFailed means detection failed at startup (force CPU for all versions—could not determine HIP vs NVIDIA); onGPUError is called when a GPU error is detected in logs (may be nil).
|
||||||
func NewContext(
|
func NewContext(
|
||||||
taskID, jobID int64,
|
taskID, jobID int64,
|
||||||
jobName string,
|
jobName string,
|
||||||
@@ -65,6 +71,8 @@ func NewContext(
|
|||||||
encoder *encoding.Selector,
|
encoder *encoding.Selector,
|
||||||
processes *executils.ProcessTracker,
|
processes *executils.ProcessTracker,
|
||||||
gpuLockedOut bool,
|
gpuLockedOut bool,
|
||||||
|
hasHIP bool,
|
||||||
|
gpuDetectionFailed bool,
|
||||||
onGPUError func(),
|
onGPUError func(),
|
||||||
) *Context {
|
) *Context {
|
||||||
if frameEnd < frameStart {
|
if frameEnd < frameStart {
|
||||||
@@ -87,6 +95,8 @@ func NewContext(
|
|||||||
Encoder: encoder,
|
Encoder: encoder,
|
||||||
Processes: processes,
|
Processes: processes,
|
||||||
GPULockedOut: gpuLockedOut,
|
GPULockedOut: gpuLockedOut,
|
||||||
|
HasHIP: hasHIP,
|
||||||
|
GPUDetectionFailed: gpuDetectionFailed,
|
||||||
OnGPUError: onGPUError,
|
OnGPUError: onGPUError,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,11 +179,23 @@ func (c *Context) ShouldEnableExecution() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ShouldForceCPU returns true if GPU should be disabled and CPU rendering forced
|
// ShouldForceCPU returns true if GPU should be disabled and CPU rendering forced
|
||||||
// (runner GPU lockout or metadata force_cpu in engine_settings).
|
// (runner GPU lockout, GPU detection failed at startup for any version, metadata force_cpu,
|
||||||
|
// or Blender < 4.x when the runner has HIP).
|
||||||
func (c *Context) ShouldForceCPU() bool {
|
func (c *Context) ShouldForceCPU() bool {
|
||||||
if c.GPULockedOut {
|
if c.GPULockedOut {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
// Detection failed at startup: we could not determine HIP vs NVIDIA, so force CPU for all versions.
|
||||||
|
if c.GPUDetectionFailed {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
v := c.GetBlenderVersion()
|
||||||
|
major := parseBlenderMajor(v)
|
||||||
|
isPre4 := v != "" && major >= 0 && major < 4
|
||||||
|
// Blender < 4.x: force CPU when runner has HIP (no official HIP support).
|
||||||
|
if isPre4 && c.HasHIP {
|
||||||
|
return true
|
||||||
|
}
|
||||||
if c.Metadata != nil && c.Metadata.RenderSettings.EngineSettings != nil {
|
if c.Metadata != nil && c.Metadata.RenderSettings.EngineSettings != nil {
|
||||||
if v, ok := c.Metadata.RenderSettings.EngineSettings["force_cpu"]; ok {
|
if v, ok := c.Metadata.RenderSettings.EngineSettings["force_cpu"]; ok {
|
||||||
if b, ok := v.(bool); ok && b {
|
if b, ok := v.(bool); ok && b {
|
||||||
@@ -184,6 +206,21 @@ func (c *Context) ShouldForceCPU() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseBlenderMajor returns the major version number from a string like "4.2.3" or "3.6".
|
||||||
|
// Returns -1 if the version cannot be parsed.
|
||||||
|
func parseBlenderMajor(version string) int {
|
||||||
|
version = strings.TrimSpace(version)
|
||||||
|
if version == "" {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
parts := strings.SplitN(version, ".", 2)
|
||||||
|
major, err := strconv.Atoi(parts[0])
|
||||||
|
if err != nil {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return major
|
||||||
|
}
|
||||||
|
|
||||||
// IsJobCancelled checks whether the manager marked this job as cancelled.
|
// IsJobCancelled checks whether the manager marked this job as cancelled.
|
||||||
func (c *Context) IsJobCancelled() (bool, error) {
|
func (c *Context) IsJobCancelled() (bool, error) {
|
||||||
if c.Manager == nil {
|
if c.Manager == nil {
|
||||||
|
|||||||
@@ -104,8 +104,17 @@ func (p *RenderProcessor) Process(ctx *Context) error {
|
|||||||
renderFormat := "EXR"
|
renderFormat := "EXR"
|
||||||
|
|
||||||
if ctx.ShouldForceCPU() {
|
if ctx.ShouldForceCPU() {
|
||||||
|
v := ctx.GetBlenderVersion()
|
||||||
|
major := parseBlenderMajor(v)
|
||||||
|
isPre4 := v != "" && major >= 0 && major < 4
|
||||||
|
if ctx.GPUDetectionFailed {
|
||||||
|
ctx.Info("GPU backend detection failed at startup—we could not determine whether this machine has HIP (AMD) or NVIDIA GPUs, so rendering will use CPU to avoid compatibility issues")
|
||||||
|
} else if isPre4 && ctx.HasHIP {
|
||||||
|
ctx.Info("Blender < 4.x has no official HIP support: using CPU rendering only")
|
||||||
|
} else {
|
||||||
ctx.Info("GPU lockout active: using CPU rendering only")
|
ctx.Info("GPU lockout active: using CPU rendering only")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create render script
|
// Create render script
|
||||||
if err := p.createRenderScript(ctx, renderFormat); err != nil {
|
if err := p.createRenderScript(ctx, renderFormat); err != nil {
|
||||||
@@ -217,9 +226,9 @@ func (p *RenderProcessor) runBlender(ctx *Context, blenderBinary, blendFile, out
|
|||||||
cmd := exec.Command(blenderBinary, args...)
|
cmd := exec.Command(blenderBinary, args...)
|
||||||
cmd.Dir = ctx.WorkDir
|
cmd.Dir = ctx.WorkDir
|
||||||
|
|
||||||
// Set up environment with custom HOME directory
|
// Set up environment: LD_LIBRARY_PATH for tarball Blender, then custom HOME
|
||||||
env := os.Environ()
|
env := os.Environ()
|
||||||
// Remove existing HOME if present and add our custom one
|
env = blender.TarballEnv(blenderBinary, env)
|
||||||
newEnv := make([]string, 0, len(env)+1)
|
newEnv := make([]string, 0, len(env)+1)
|
||||||
for _, e := range env {
|
for _, e := range env {
|
||||||
if !strings.HasPrefix(e, "HOME=") {
|
if !strings.HasPrefix(e, "HOME=") {
|
||||||
|
|||||||
@@ -11,3 +11,6 @@ var UnhideObjects string
|
|||||||
//go:embed scripts/render_blender.py.template
|
//go:embed scripts/render_blender.py.template
|
||||||
var RenderBlenderTemplate string
|
var RenderBlenderTemplate string
|
||||||
|
|
||||||
|
//go:embed scripts/detect_gpu_backends.py
|
||||||
|
var DetectGPUBackends string
|
||||||
|
|
||||||
|
|||||||
39
pkg/scripts/scripts/detect_gpu_backends.py
Normal file
39
pkg/scripts/scripts/detect_gpu_backends.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Minimal script to detect HIP (AMD) and NVIDIA (CUDA/OptiX) backends for Cycles.
|
||||||
|
# Run with: blender -b --python detect_gpu_backends.py
|
||||||
|
# Prints HAS_HIP and/or HAS_NVIDIA to stdout, one per line.
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def main():
|
||||||
|
try:
|
||||||
|
prefs = bpy.context.preferences
|
||||||
|
if not hasattr(prefs, 'addons') or 'cycles' not in prefs.addons:
|
||||||
|
return
|
||||||
|
cprefs = prefs.addons['cycles'].preferences
|
||||||
|
has_hip = False
|
||||||
|
has_nvidia = False
|
||||||
|
for device_type in ('HIP', 'CUDA', 'OPTIX'):
|
||||||
|
try:
|
||||||
|
cprefs.compute_device_type = device_type
|
||||||
|
cprefs.refresh_devices()
|
||||||
|
devs = []
|
||||||
|
if hasattr(cprefs, 'get_devices'):
|
||||||
|
devs = cprefs.get_devices()
|
||||||
|
elif hasattr(cprefs, 'devices') and cprefs.devices:
|
||||||
|
devs = list(cprefs.devices) if hasattr(cprefs.devices, '__iter__') else [cprefs.devices]
|
||||||
|
if devs:
|
||||||
|
if device_type == 'HIP':
|
||||||
|
has_hip = True
|
||||||
|
if device_type in ('CUDA', 'OPTIX'):
|
||||||
|
has_nvidia = True
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if has_hip:
|
||||||
|
print('HAS_HIP', flush=True)
|
||||||
|
if has_nvidia:
|
||||||
|
print('HAS_NVIDIA', flush=True)
|
||||||
|
except Exception as e:
|
||||||
|
print('ERROR', str(e), file=sys.stderr, flush=True)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user