{"slug": "your-gan-beauty-effect-needs-a-device-budget-not-a-universal-on-switch", "title": "Your GAN Beauty Effect Needs a Device Budget, Not a Universal “On” Switch", "summary": "Tencent RTC's Beauty AR documentation and low-end optimization guide recommend that developers implement device-aware policies for GAN-powered beauty effects rather than enabling all features universally. A developer demonstrates an application-owned Beauty AR controller in TypeScript that separates consent, capability probing, and fallback states to avoid degrading performance on constrained devices.", "body_md": "A GAN-powered beauty effect can look convincing in a product demo and still be the wrong default for a real session.\n\nThe uncomfortable part is not whether the effect is “AI.” It is deciding what the application should do when appearance processing, segmentation, rendering, and video compete for a limited device budget. If the answer is simply “enable everything and hope,” lower-capability devices pay the price.\n\nThis is also where writing less integration code can create more engineering responsibility. The durable skill is not producing another effect toggle. It is defining consent, capability, fallback, and verification rules that remain understandable when the renderer fails.\n\nIn this tutorial, we will build an application-owned Beauty AR controller that:\n\nTencent RTC Beauty AR supports scenarios including real-time beauty filters, makeup, stickers, virtual backgrounds, avatars, gesture recognition, and image or video enhancement. The official overview is the appropriate starting point for checking the features available to your integration:\n\n[https://trtc.io/document/beauty-ar-overview](https://trtc.io/document/beauty-ar-overview)\n\nFor device-tier and degradation guidance, Tencent RTC’s low-end optimization guide recommends adapting the configuration to device capability, using performance-oriented modes, controlling resolution and frame rate, and disabling expensive segmentation or 3D/GAN effects where necessary:\n\n[https://trtc.io/document/66968](https://trtc.io/document/66968)\n\nThe code below is deliberately an application policy, not a replacement for the official platform-specific integration instructions.\n\nA useful Beauty AR contract separates three decisions that are often collapsed into one checkbox:\n\nA user selecting “Full” should not force a constrained device to run every effect. It means the application may use the richest profile that its capability policy currently permits.\n\nWe will use these states:\n\n``` php\nawaiting-consent\n       |\n       +-- denied --------------------------> off\n       |\n       +-- granted --> probing --> applying --> running\n                                      |            |\n                                      |            +-- sustained pressure\n                                      |                       |\n                                      +-- failure             v\n                                             applying safer profile\n                                                        |\n                                                        +--> degraded\n                                                        |\n                                                        +--> failed --> off\n```\n\nThe distinction between `degraded`\n\nand `failed`\n\nmatters. Degraded means the session is still providing an intentionally reduced visual experience. Failed means no approved profile could be applied safely, so the effects are off.\n\nUse Node.js with TypeScript for the policy and tests:\n\n```\nmkdir beauty-budget\ncd beauty-budget\nnpm init -y\nnpm install --save-dev typescript tsx @types/node\nmkdir src test\n```\n\nAdd these scripts to `package.json`\n\n:\n\n```\n{\n  \"scripts\": {\n    \"test\": \"tsx --test test/**/*.test.ts\"\n  }\n}\n```\n\nThe policy can be tested without a camera, a live room, or a specific Beauty AR SDK method. That is intentional: renderer callbacks should provide evidence to the policy, not contain the policy themselves.\n\nCreate `src/policy.ts`\n\n:\n\n```\nexport type Tier = \"constrained\" | \"balanced\" | \"capable\";\nexport type Preference = \"off\" | \"auto\" | \"basic\" | \"full\";\nexport type Phase =\n  | \"awaiting-consent\"\n  | \"probing\"\n  | \"applying\"\n  | \"running\"\n  | \"degraded\"\n  | \"off\"\n  | \"failed\";\n\nexport type Feature =\n  | \"beauty\"\n  | \"makeup\"\n  | \"stickers\"\n  | \"segmentation\"\n  | \"gan\"\n  | \"avatar\";\n\nexport interface RenderProfile {\n  id: string;\n  mode: \"performance\" | \"quality\";\n  inputHeight: number;\n  targetFps: number;\n  features: Feature[];\n}\n\nconst tierProfiles: Record<Tier, RenderProfile> = {\n  constrained: {\n    id: \"constrained-v1\",\n    mode: \"performance\",\n    inputHeight: 480,\n    targetFps: 15,\n    features: [\"beauty\"]\n  },\n  balanced: {\n    id: \"balanced-v1\",\n    mode: \"performance\",\n    inputHeight: 720,\n    targetFps: 24,\n    features: [\"beauty\", \"makeup\", \"stickers\"]\n  },\n  capable: {\n    id: \"capable-v1\",\n    mode: \"quality\",\n    inputHeight: 720,\n    targetFps: 30,\n    features: [\n      \"beauty\",\n      \"makeup\",\n      \"stickers\",\n      \"segmentation\",\n      \"gan\",\n      \"avatar\"\n    ]\n  }\n};\n\nfunction profileFor(tier: Tier, preference: Preference): RenderProfile {\n  const base = tierProfiles[tier];\n\n  if (preference === \"basic\") {\n    return {\n      ...base,\n      id: `${base.id}-basic`,\n      features: base.features.filter(feature => feature === \"beauty\")\n    };\n  }\n\n  return { ...base, features: [...base.features] };\n}\n\nexport interface State {\n  phase: Phase;\n  consent: \"unknown\" | \"granted\" | \"denied\";\n  preference: Preference;\n  tier?: Tier;\n  profile?: RenderProfile;\n  revision: number;\n  badWindows: number;\n  hasDegraded: boolean;\n  error?: string;\n}\n\nexport type Event =\n  | { type: \"CONSENT_GRANTED\" }\n  | { type: \"CONSENT_DENIED\" }\n  | { type: \"PROBE_COMPLETED\"; tier: Tier }\n  | { type: \"PREFERENCE_CHANGED\"; preference: Preference }\n  | { type: \"FRAME_WINDOW\"; observedFps: number }\n  | { type: \"PROFILE_APPLIED\"; revision: number }\n  | { type: \"PROFILE_FAILED\"; revision: number; error: string };\n\nexport type Command =\n  | { type: \"APPLY_PROFILE\"; revision: number; profile: RenderProfile }\n  | { type: \"DISABLE_EFFECTS\" };\n\nexport function initialState(): State {\n  return {\n    phase: \"awaiting-consent\",\n    consent: \"unknown\",\n    preference: \"auto\",\n    revision: 0,\n    badWindows: 0,\n    hasDegraded: false\n  };\n}\n\nfunction lowerTier(tier: Tier): Tier | undefined {\n  if (tier === \"capable\") return \"balanced\";\n  if (tier === \"balanced\") return \"constrained\";\n  return undefined;\n}\n\nfunction startApply(\n  state: State,\n  tier: Tier,\n  degraded: boolean\n): [State, Command[]] {\n  const revision = state.revision + 1;\n  const profile = profileFor(tier, state.preference);\n\n  return [\n    {\n      ...state,\n      phase: \"applying\",\n      tier,\n      profile,\n      revision,\n      badWindows: 0,\n      hasDegraded: state.hasDegraded || degraded,\n      error: undefined\n    },\n    [{ type: \"APPLY_PROFILE\", revision, profile }]\n  ];\n}\n\nexport function reduce(state: State, event: Event): [State, Command[]] {\n  switch (event.type) {\n    case \"CONSENT_GRANTED\":\n      return [\n        { ...state, consent: \"granted\", phase: \"probing\", error: undefined },\n        []\n      ];\n\n    case \"CONSENT_DENIED\":\n      return [\n        {\n          ...state,\n          consent: \"denied\",\n          preference: \"off\",\n          phase: \"off\",\n          profile: undefined,\n          revision: state.revision + 1,\n          badWindows: 0\n        },\n        [{ type: \"DISABLE_EFFECTS\" }]\n      ];\n\n    case \"PROBE_COMPLETED\":\n      if (state.consent !== \"granted\" || state.preference === \"off\") {\n        return [state, []];\n      }\n      return startApply(state, event.tier, false);\n\n    case \"PREFERENCE_CHANGED\": {\n      if (event.preference === \"off\") {\n        return [\n          {\n            ...state,\n            preference: \"off\",\n            phase: \"off\",\n            profile: undefined,\n            revision: state.revision + 1,\n            badWindows: 0\n          },\n          [{ type: \"DISABLE_EFFECTS\" }]\n        ];\n      }\n\n      const next = { ...state, preference: event.preference };\n\n      if (next.consent !== \"granted\") return [next, []];\n      if (!next.tier) return [{ ...next, phase: \"probing\" }, []];\n\n      return startApply(next, next.tier, false);\n    }\n\n    case \"FRAME_WINDOW\": {\n      if (\n        (state.phase !== \"running\" && state.phase !== \"degraded\") ||\n        !state.profile ||\n        !state.tier\n      ) {\n        return [state, []];\n      }\n\n      // An application-owned starting threshold, not a product benchmark.\n      const belowBudget = event.observedFps < state.profile.targetFps * 0.8;\n      const badWindows = belowBudget ? state.badWindows + 1 : 0;\n\n      if (badWindows < 3) {\n        return [{ ...state, badWindows }, []];\n      }\n\n      const saferTier = lowerTier(state.tier);\n\n      if (!saferTier) {\n        return [\n          {\n            ...state,\n            phase: \"failed\",\n            profile: undefined,\n            badWindows: 0,\n            error: \"Frame budget was not sustained on the safest profile\"\n          },\n          [{ type: \"DISABLE_EFFECTS\" }]\n        ];\n      }\n\n      return startApply({ ...state, badWindows: 0 }, saferTier, true);\n    }\n\n    case \"PROFILE_APPLIED\":\n      // The user may have turned effects off while an apply was in flight.\n      if (event.revision !== state.revision || state.phase !== \"applying\") {\n        return [state, []];\n      }\n\n      return [\n        {\n          ...state,\n          phase: state.hasDegraded ? \"degraded\" : \"running\",\n          badWindows: 0\n        },\n        []\n      ];\n\n    case \"PROFILE_FAILED\": {\n      if (event.revision !== state.revision || state.phase !== \"applying\") {\n        return [state, []];\n      }\n\n      const saferTier = state.tier ? lowerTier(state.tier) : undefined;\n\n      if (saferTier) {\n        return startApply(\n          { ...state, error: event.error },\n          saferTier,\n          true\n        );\n      }\n\n      return [\n        {\n          ...state,\n          phase: \"failed\",\n          profile: undefined,\n          error: event.error\n        },\n        [{ type: \"DISABLE_EFFECTS\" }]\n      ];\n    }\n  }\n}\n```\n\nThe numeric profile values and the 80% threshold are example application defaults, not Tencent RTC performance guarantees. Calibrate them using measurements from the devices your application actually supports.\n\nThe more important property is the ordering:\n\n``` php\ncapable -> balanced -> constrained -> effects off\n```\n\nGAN and segmentation disappear before basic beauty processing does. The user still gets a valid session rather than an all-or-nothing renderer.\n\nKeep platform-specific SDK calls behind a narrow adapter:\n\n``` python\nimport type { Command, Event, RenderProfile } from \"./policy.js\";\n\nexport interface BeautyRenderer {\n  apply(profile: RenderProfile): Promise<void>;\n  disable(): Promise<void>;\n}\n\nexport async function execute(\n  command: Command,\n  renderer: BeautyRenderer,\n  dispatch: (event: Event) => void\n): Promise<void> {\n  if (command.type === \"DISABLE_EFFECTS\") {\n    await renderer.disable();\n    return;\n  }\n\n  try {\n    await renderer.apply(command.profile);\n    dispatch({\n      type: \"PROFILE_APPLIED\",\n      revision: command.revision\n    });\n  } catch (error) {\n    dispatch({\n      type: \"PROFILE_FAILED\",\n      revision: command.revision,\n      error: error instanceof Error ? error.message : \"Unknown renderer error\"\n    });\n  }\n}\n```\n\n`BeautyRenderer.apply`\n\nis an application-defined port, not a Tencent RTC API name. Its concrete implementation should map the selected profile to the documented Beauty AR configuration for your target platform.\n\nThat adapter is also the right place to normalize errors. A production implementation should distinguish at least:\n\nA permanent configuration error should normally stop immediately rather than trying every performance tier. The generic sample falls back because it cannot know the platform-specific error taxonomy.\n\nDo not infer “capable” solely from a device model or user-agent string. Two nominally identical devices can differ because of thermal state, background load, browser behavior, camera configuration, or power settings.\n\nA practical probe can combine:\n\nRun the probe only after consent, and avoid publishing or retaining camera frames merely to classify the device. The policy needs a tier result, not the images used to produce it.\n\nUse a conservative tier when measurement is missing. “Unknown” should not silently mean “capable.”\n\nThere is another measurement trap: poor remote video can come from the network, encoder, decoder, or receiver. Do not downgrade local Beauty AR solely because a remote participant reports low frame rate. Feed this policy measurements attributable to the local rendering path.\n\nCreate `test/policy.test.ts`\n\n:\n\n``` python\nimport test from \"node:test\";\nimport assert from \"node:assert/strict\";\nimport { initialState, reduce, type State } from \"../src/policy.js\";\n\nfunction grantAndProbe(tier: \"constrained\" | \"balanced\" | \"capable\") {\n  let state = initialState();\n  [state] = reduce(state, { type: \"CONSENT_GRANTED\" });\n  const [applying, commands] = reduce(state, {\n    type: \"PROBE_COMPLETED\",\n    tier\n  });\n\n  return { state: applying, commands };\n}\n\ntest(\"does not apply a profile before consent\", () => {\n  const [state, commands] = reduce(initialState(), {\n    type: \"PROBE_COMPLETED\",\n    tier: \"capable\"\n  });\n\n  assert.equal(state.phase, \"awaiting-consent\");\n  assert.equal(commands.length, 0);\n});\n\ntest(\"a capable device may receive the GAN profile\", () => {\n  const { state, commands } = grantAndProbe(\"capable\");\n\n  assert.equal(state.phase, \"applying\");\n  assert.equal(state.profile?.features.includes(\"gan\"), true);\n  assert.equal(commands[0]?.type, \"APPLY_PROFILE\");\n});\n\ntest(\"three bad windows cause one downgrade\", () => {\n  let { state } = grantAndProbe(\"capable\");\n  [state] = reduce(state, {\n    type: \"PROFILE_APPLIED\",\n    revision: state.revision\n  });\n\n  let commands = [] as ReturnType<typeof reduce>[1];\n\n  for (let i = 0; i < 3; i++) {\n    [state, commands] = reduce(state, {\n      type: \"FRAME_WINDOW\",\n      observedFps: 10\n    });\n  }\n\n  assert.equal(state.tier, \"balanced\");\n  assert.equal(state.phase, \"applying\");\n  assert.equal(state.hasDegraded, true);\n  assert.equal(commands[0]?.type, \"APPLY_PROFILE\");\n  assert.equal(state.profile?.features.includes(\"gan\"), false);\n});\n\ntest(\"an apply failure tries a safer tier\", () => {\n  let { state } = grantAndProbe(\"balanced\");\n  const failedRevision = state.revision;\n\n  const [next, commands] = reduce(state, {\n    type: \"PROFILE_FAILED\",\n    revision: failedRevision,\n    error: \"renderer initialization failed\"\n  });\n\n  assert.equal(next.tier, \"constrained\");\n  assert.equal(next.phase, \"applying\");\n  assert.equal(commands[0]?.type, \"APPLY_PROFILE\");\n});\n\ntest(\"a late success cannot resurrect effects after user turns them off\", () => {\n  let state: State = grantAndProbe(\"capable\").state;\n  const oldRevision = state.revision;\n\n  [state] = reduce(state, {\n    type: \"PREFERENCE_CHANGED\",\n    preference: \"off\"\n  });\n\n  [state] = reduce(state, {\n    type: \"PROFILE_APPLIED\",\n    revision: oldRevision\n  });\n\n  assert.equal(state.phase, \"off\");\n  assert.equal(state.profile, undefined);\n});\n```\n\nRun the suite:\n\n```\nnpm test\n```\n\nThese tests verify the policy’s invariants rather than whether one machine can render a particular effect.\n\nAn obvious extension is to upgrade again as soon as frame rate recovers. That usually creates a new problem: oscillation.\n\nA device may briefly recover after an expensive effect is disabled. If the application immediately restores the effect, frame pressure returns, causing another downgrade. The user sees repeated visual changes while the renderer does unnecessary work.\n\nA safer default is:\n\nIf you do implement automatic recovery, require substantially more good evidence than bad evidence. For example, degradation might require three poor windows while recovery requires a much longer stable interval. Those values still need calibration rather than guesswork.\n\nThe revision check prevents a late `PROFILE_APPLIED`\n\nevent from moving the application back to `running`\n\n. The renderer adapter must also release loaded resources and stop processing.\n\nChoose the conservative profile. Do not translate missing data into permission to enable segmentation, avatars, or GAN effects.\n\nThe sample tries the constrained profile. In production, classify errors first. A missing credential or invalid configuration will not be repaired by reducing resolution.\n\nDisable Beauty AR while preserving the underlying call or live session. Beauty effects are optional; communication should not depend on them.\n\nGraphics resources may no longer be in the state your controller expects. Treat restoration as revalidation: check consent, verify that the revision is still current, and reapply no more than the last approved tier.\n\nKeep the constrained profile and explain why richer effects are unavailable. Do not quietly pretend the GAN effect is active, but do not override the safety policy either.\n\nGAN-based visual processing can produce effects that would be difficult to implement with simple color adjustments. That demonstrated capability does not answer the product questions around it:\n\nThose are application and human decisions, not outputs to delegate to a model.\n\nFor many developers, the underlying tension is that integrating an advanced effect can now take less code, while being confident in the result requires more judgment. That is not a loss of engineering value. The valuable work has moved toward defining constraints, making degradation visible, and proving that user control survives asynchronous failure.\n\nBefore shipping, verify all of the following:\n\nThe goal is not to make every device render the most impressive effect. It is to make every outcome intentional: rich where supported, restrained where necessary, off when requested, and recoverable when rendering fails.\n\n**Disclosure:** I wrote this article in connection with Tencent RTC, and I used the official Tencent RTC Beauty AR documentation as the implementation reference.", "url": "https://wpnews.pro/news/your-gan-beauty-effect-needs-a-device-budget-not-a-universal-on-switch", "canonical_source": "https://dev.to/susiewang/your-gan-beauty-effect-needs-a-device-budget-not-a-universal-on-switch-2njp", "published_at": "2026-08-20 04:12:41+00:00", "updated_at": "2026-08-20 04:43:21.569934+00:00", "lang": "en", "topics": ["developer-tools", "artificial-intelligence", "generative-ai"], "entities": ["Tencent RTC"], "alternates": {"html": "https://wpnews.pro/news/your-gan-beauty-effect-needs-a-device-budget-not-a-universal-on-switch", "markdown": "https://wpnews.pro/news/your-gan-beauty-effect-needs-a-device-budget-not-a-universal-on-switch.md", "text": "https://wpnews.pro/news/your-gan-beauty-effect-needs-a-device-budget-not-a-universal-on-switch.txt", "jsonld": "https://wpnews.pro/news/your-gan-beauty-effect-needs-a-device-budget-not-a-universal-on-switch.jsonld"}}